Skip to content

Commit cb1a893

Browse files
Add support for 1.20.4 fixed formatting (#46)
Co-authored-by: MrMicky <git@mrmicky.fr>
1 parent 87ffa56 commit cb1a893

File tree

3 files changed

+184
-15
lines changed

3 files changed

+184
-15
lines changed

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.20.
1313

1414
* No flickering (without using a buffer)
1515
* Works with all versions from 1.7.10 to 1.20
16-
* Very small (around 600 lines of code with the JavaDoc) and no dependencies
16+
* Small (around 750 lines of code with the JavaDoc) and no dependencies
1717
* Easy to use
1818
* Dynamic scoreboard size: you don't need to add/remove lines, you can directly give a string list (or array) to change all the lines
1919
* Everything is at the packet level, so it works with other plugins using scoreboard and/or teams
2020
* Can be used asynchronously
2121
* Supports up to 30 characters per line on 1.12.2 and below
2222
* No character limit on 1.13 and higher
23-
* Supports hex colors on 1.16 and higher
24-
* No scoreboard scores on 1.20.3 and higher
25-
* [Adventure](https://github.com/KyoriPowered/adventure) components support
23+
* [RGB HEX colors support](#rgb-colors) on 1.16 and higher
24+
* [Custom number formatting](#custom-number-formatting) (including blank) for scores on 1.20.3 and higher
25+
* [Adventure components support](#adventure-support)
2626

2727
## Installation
2828

@@ -59,7 +59,7 @@ Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.20.
5959
<dependency>
6060
<groupId>fr.mrmicky</groupId>
6161
<artifactId>fastboard</artifactId>
62-
<version>2.0.2</version>
62+
<version>2.1.0</version>
6363
</dependency>
6464
</dependencies>
6565
```
@@ -79,7 +79,7 @@ repositories {
7979
}
8080
8181
dependencies {
82-
implementation 'fr.mrmicky:fastboard:2.0.2'
82+
implementation 'fr.mrmicky:fastboard:2.1.0'
8383
}
8484
8585
shadowJar {
@@ -186,20 +186,28 @@ public final class ExamplePlugin extends JavaPlugin implements Listener {
186186
## Adventure support
187187

188188
For servers on modern [PaperMC](https://papermc.io) versions, FastBoard supports
189-
using [Adventure](https://github.com/KyoriPowered/adventure) components instead of strings,
189+
using [Adventure](https://github.com/KyoriPowered/adventure) components instead of strings,
190190
by using the class `fr.mrmicky.fastboard.adventure.FastBoard`.
191191

192192
## RGB colors
193193

194194
When using the non-Adventure version of FastBoard, RGB colors can be added on 1.16 and higher with `ChatColor.of("#RRGGBB")` (`net.md_5.bungee.api.ChatColor` import).
195195

196+
## Custom number formatting
197+
198+
For servers on Minecraft 1.20.3 and higher, FastBoard supports custom number formatting for scores.
199+
By default, the blank format is used, so no score is visible, but it's also possible to specify custom scores using `FastBoard#updateLine(line, text, scoreText)`,
200+
`FastBoard#updateLines(lines, scores)` and `FastBoard#updateScore(line, text)`.
201+
202+
Passing a `null` value as a score will result in a reset to the default blank formatting.
203+
196204
## ViaBackwards compatibility
197205

198206
When using ViaBackwards on a post-1.13 server with pre-1.13 clients, older clients
199207
might get incomplete lines. To solve this issue, you can override the method `hasLinesMaxLength()` and return `true` for older clients.
200208
For example using the ViaVersion API:
201209
```java
202-
FastBoard board = new FastBoard(player) {
210+
FastBoard board = new FastBoard(player) {
203211
@Override
204212
public boolean hasLinesMaxLength() {
205213
return Via.getAPI().getPlayerVersion(getPlayer()) < ProtocolVersion.v1_13.getVersion(); // or just 'return true;'

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>fr.mrmicky</groupId>
88
<artifactId>fastboard</artifactId>
9-
<version>2.0.2</version>
9+
<version>2.1.0</version>
1010

1111
<name>FastBoard</name>
1212
<description>Lightweight packet-based scoreboard API for Bukkit plugins.</description>

src/main/java/fr/mrmicky/fastboard/FastBoardBase.java

Lines changed: 167 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* The project is on <a href="https://github.com/MrMicky-FR/FastBoard">GitHub</a>.
4444
*
4545
* @author MrMicky
46-
* @version 2.0.2
46+
* @version 2.1.0
4747
*/
4848
public abstract class FastBoardBase<T> {
4949

@@ -59,6 +59,7 @@ public abstract class FastBoardBase<T> {
5959
private static final MethodHandle PLAYER_CONNECTION;
6060
private static final MethodHandle SEND_PACKET;
6161
private static final MethodHandle PLAYER_GET_HANDLE;
62+
private static final MethodHandle FIXED_NUMBER_FORMAT;
6263
// Scoreboard packets
6364
private static final FastReflection.PacketConstructor PACKET_SB_OBJ;
6465
private static final FastReflection.PacketConstructor PACKET_SB_DISPLAY_OBJ;
@@ -125,14 +126,18 @@ public abstract class FastBoardBase<T> {
125126
Optional<Class<?>> numberFormat = FastReflection.nmsOptionalClass("network.chat.numbers", "NumberFormat");
126127
MethodHandle packetSbSetScore;
127128
MethodHandle packetSbResetScore = null;
129+
MethodHandle fixedFormatConstructor = null;
128130
Object blankNumberFormat = null;
129131

130132
if (numberFormat.isPresent()) { // 1.20.3
131133
Class<?> blankFormatClass = FastReflection.nmsClass("network.chat.numbers", "BlankFormat");
134+
Class<?> fixedFormatClass = FastReflection.nmsClass("network.chat.numbers", "FixedFormat");
132135
Class<?> resetScoreClass = FastReflection.nmsClass(gameProtocolPackage, "ClientboundResetScorePacket");
133136
MethodType setScoreType = MethodType.methodType(void.class, String.class, String.class, int.class, CHAT_COMPONENT_CLASS, numberFormat.get());
134137
MethodType removeScoreType = MethodType.methodType(void.class, String.class, String.class);
138+
MethodType fixedFormatType = MethodType.methodType(void.class, CHAT_COMPONENT_CLASS);
135139
Optional<Field> blankField = Arrays.stream(blankFormatClass.getFields()).filter(f -> f.getType() == blankFormatClass).findAny();
140+
fixedFormatConstructor = lookup.findConstructor(fixedFormatClass, fixedFormatType);
136141
packetSbSetScore = lookup.findConstructor(packetSbScoreClass, setScoreType);
137142
packetSbResetScore = lookup.findConstructor(resetScoreClass, removeScoreType);
138143
blankNumberFormat = blankField.isPresent() ? blankField.get().get(null) : null;
@@ -148,6 +153,7 @@ public abstract class FastBoardBase<T> {
148153
PACKET_SB_RESET_SCORE = packetSbResetScore;
149154
PACKET_SB_TEAM = FastReflection.findPacketConstructor(packetSbTeamClass, lookup);
150155
PACKET_SB_SERIALIZABLE_TEAM = sbTeamClass == null ? null : FastReflection.findPacketConstructor(sbTeamClass, lookup);
156+
FIXED_NUMBER_FORMAT = fixedFormatConstructor;
151157
BLANK_NUMBER_FORMAT = blankNumberFormat;
152158

153159
for (Class<?> clazz : Arrays.asList(packetSbObjClass, packetSbDisplayObjClass, packetSbScoreClass, packetSbTeamClass, sbTeamClass)) {
@@ -188,6 +194,7 @@ public abstract class FastBoardBase<T> {
188194
private final String id;
189195

190196
private final List<T> lines = new ArrayList<>();
197+
private final List<T> scores = new ArrayList<>();
191198
private T title = emptyLine();
192199

193200
private boolean deleted = false;
@@ -261,6 +268,19 @@ public T getLine(int line) {
261268
return this.lines.get(line);
262269
}
263270

271+
/**
272+
* Get how a specific line's score is displayed. On 1.20.2 or below, the value returned isn't used.
273+
*
274+
* @param line the line number
275+
* @return the text of how the line is displayed
276+
* @throws IndexOutOfBoundsException if the line is higher than {@code size}
277+
*/
278+
public Optional<T> getScore(int line) {
279+
checkLineNumber(line, true, false);
280+
281+
return Optional.ofNullable(this.scores.get(line));
282+
}
283+
264284
/**
265285
* Update a single scoreboard line.
266286
*
@@ -269,27 +289,49 @@ public T getLine(int line) {
269289
* @throws IndexOutOfBoundsException if the line is higher than {@link #size() size() + 1}
270290
*/
271291
public synchronized void updateLine(int line, T text) {
272-
checkLineNumber(line, false, true);
292+
updateLine(line, text, null);
293+
}
294+
295+
/**
296+
* Update a single scoreboard line including how its score is displayed.
297+
* The score will only be displayed on 1.20.3 and higher.
298+
*
299+
* @param line the line number
300+
* @param text the new line text
301+
* @param scoreText the new line's score, if null will not change current value
302+
* @throws IndexOutOfBoundsException if the line is higher than {@link #size() size() + 1}
303+
*/
304+
public synchronized void updateLine(int line, T text, T scoreText) {
305+
checkLineNumber(line, false, false);
273306

274307
try {
275308
if (line < size()) {
276309
this.lines.set(line, text);
310+
this.scores.set(line, scoreText);
277311

278312
sendLineChange(getScoreByLine(line));
313+
314+
if (customScoresSupported()) {
315+
sendScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE);
316+
}
317+
279318
return;
280319
}
281320

282321
List<T> newLines = new ArrayList<>(this.lines);
322+
List<T> newScores = new ArrayList<>(this.scores);
283323

284324
if (line > size()) {
285325
for (int i = size(); i < line; i++) {
286326
newLines.add(emptyLine());
327+
newScores.add(null);
287328
}
288329
}
289330

290331
newLines.add(text);
332+
newScores.add(scoreText);
291333

292-
updateLines(newLines);
334+
updateLines(newLines, newScores);
293335
} catch (Throwable t) {
294336
throw new RuntimeException("Unable to update scoreboard lines", t);
295337
}
@@ -308,8 +350,10 @@ public synchronized void removeLine(int line) {
308350
}
309351

310352
List<T> newLines = new ArrayList<>(this.lines);
353+
List<T> newScores = new ArrayList<>(this.scores);
311354
newLines.remove(line);
312-
updateLines(newLines);
355+
newScores.remove(line);
356+
updateLines(newLines, newScores);
313357
}
314358

315359
/**
@@ -331,13 +375,35 @@ public void updateLines(T... lines) {
331375
* @throws IllegalStateException if {@link #delete()} was call before
332376
*/
333377
public synchronized void updateLines(Collection<T> lines) {
378+
updateLines(lines, null);
379+
}
380+
381+
/**
382+
* Update the lines and how their score is displayed on the scoreboard.
383+
* The scores will only be displayed for servers on 1.20.3 and higher.
384+
*
385+
* @param lines the new scoreboard lines
386+
* @param scores the set for how each line's score should be, if null will fall back to default (blank)
387+
* @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower
388+
* @throws IllegalArgumentException if lines and scores are not the same size
389+
* @throws IllegalStateException if {@link #delete()} was call before
390+
*/
391+
public synchronized void updateLines(Collection<T> lines, Collection<T> scores) {
334392
Objects.requireNonNull(lines, "lines");
335393
checkLineNumber(lines.size(), false, true);
336394

395+
if (scores != null && scores.size() != lines.size()) {
396+
throw new IllegalArgumentException("The size of the scores must match the size of the board");
397+
}
398+
337399
List<T> oldLines = new ArrayList<>(this.lines);
338400
this.lines.clear();
339401
this.lines.addAll(lines);
340402

403+
List<T> oldScores = new ArrayList<>(this.scores);
404+
this.scores.clear();
405+
this.scores.addAll(scores != null ? scores : Collections.nCopies(lines.size(), null));
406+
341407
int linesSize = this.lines.size();
342408

343409
try {
@@ -348,7 +414,6 @@ public synchronized void updateLines(Collection<T> lines) {
348414
for (int i = oldLinesCopy.size(); i > linesSize; i--) {
349415
sendTeamPacket(i - 1, TeamMode.REMOVE);
350416
sendScorePacket(i - 1, ScoreboardAction.REMOVE);
351-
352417
oldLines.remove(0);
353418
}
354419
} else {
@@ -363,12 +428,94 @@ public synchronized void updateLines(Collection<T> lines) {
363428
if (!Objects.equals(getLineByScore(oldLines, i), getLineByScore(i))) {
364429
sendLineChange(i);
365430
}
431+
if (!Objects.equals(getLineByScore(oldScores, i), getLineByScore(this.scores, i))) {
432+
sendScorePacket(i, ScoreboardAction.CHANGE);
433+
}
366434
}
367435
} catch (Throwable t) {
368436
throw new RuntimeException("Unable to update scoreboard lines", t);
369437
}
370438
}
371439

440+
/**
441+
* Update how a specified line's score is displayed on the scoreboard. A null value will reset the displayed
442+
* text back to default. The scores will only be displayed for servers on 1.20.3 and higher.
443+
*
444+
* @param line the line number
445+
* @param text the text to be displayed as the score. if null, no score will be displayed
446+
* @throws IllegalArgumentException if the line number is not in range
447+
* @throws IllegalStateException if {@link #delete()} was call before
448+
*/
449+
public synchronized void updateScore(int line, T text) {
450+
checkLineNumber(line, true, false);
451+
452+
this.scores.set(line, text);
453+
454+
try {
455+
if (customScoresSupported()) {
456+
sendScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE);
457+
}
458+
} catch (Throwable e) {
459+
throw new RuntimeException("Unable to update line score", e);
460+
}
461+
}
462+
463+
/**
464+
* Reset a line's score back to default (blank). The score will only be displayed for servers on 1.20.3 and higher.
465+
*
466+
* @param line the line number
467+
* @throws IllegalArgumentException if the line number is not in range
468+
* @throws IllegalStateException if {@link #delete()} was call before
469+
*/
470+
public synchronized void removeScore(int line) {
471+
updateScore(line, null);
472+
}
473+
474+
/**
475+
* Update how all lines' scores are displayed. A value of null will reset the displayed text back to default.
476+
* The scores will only be displayed for servers on 1.20.3 and higher.
477+
*
478+
* @param texts the set of texts to be displayed as the scores
479+
* @throws IllegalArgumentException if the size of the texts does not match the current size of the board
480+
* @throws IllegalStateException if {@link #delete()} was call before
481+
*/
482+
public synchronized void updateScores(T... texts) {
483+
updateScores(Arrays.asList(texts));
484+
}
485+
486+
/**
487+
* Update how all lines' scores are displayed. A null value will reset the displayed
488+
* text back to default (blank). Only available on 1.20.3+ servers.
489+
*
490+
* @param texts the set of texts to be displayed as the scores
491+
* @throws IllegalArgumentException if the size of the texts does not match the current size of the board
492+
* @throws IllegalStateException if {@link #delete()} was call before
493+
*/
494+
public synchronized void updateScores(Collection<T> texts) {
495+
Objects.requireNonNull(texts, "texts");
496+
497+
if (this.scores.size() != this.lines.size()) {
498+
throw new IllegalArgumentException("The size of the scores must match the size of the board");
499+
}
500+
501+
List<T> newScores = new ArrayList<>(texts);
502+
for (int i = 0; i < this.scores.size(); i++) {
503+
if (Objects.equals(this.scores.get(i), newScores.get(i))) {
504+
continue;
505+
}
506+
507+
this.scores.set(i, newScores.get(i));
508+
509+
try {
510+
if (customScoresSupported()) {
511+
sendScorePacket(getScoreByLine(i), ScoreboardAction.CHANGE);
512+
}
513+
} catch (Throwable e) {
514+
throw new RuntimeException("Unable to update scores", e);
515+
}
516+
}
517+
}
518+
372519
/**
373520
* Get the player who has the scoreboard.
374521
*
@@ -396,6 +543,15 @@ public boolean isDeleted() {
396543
return this.deleted;
397544
}
398545

546+
/**
547+
* Get if the server supports custom scoreboard scores (1.20.3+ servers only).
548+
*
549+
* @return true if the server supports custom scores
550+
*/
551+
public boolean customScoresSupported() {
552+
return BLANK_NUMBER_FORMAT != null;
553+
}
554+
399555
/**
400556
* Get the scoreboard size (the number of lines).
401557
*
@@ -528,7 +684,12 @@ private void sendModernScorePacket(int score, ScoreboardAction action) throws Th
528684
return;
529685
}
530686

531-
sendPacket(PACKET_SB_SET_SCORE.invoke(objName, this.id, score, null, BLANK_NUMBER_FORMAT));
687+
T scoreFormat = getLineByScore(this.scores, score);
688+
Object format = scoreFormat != null
689+
? FIXED_NUMBER_FORMAT.invoke(toMinecraftComponent(scoreFormat))
690+
: BLANK_NUMBER_FORMAT;
691+
692+
sendPacket(PACKET_SB_SET_SCORE.invoke(objName, this.id, score, null, format));
532693
}
533694

534695
protected void sendTeamPacket(int score, TeamMode mode) throws Throwable {

0 commit comments

Comments
 (0)