From f05d50f0e7670156b96763d5e69a49fccb24009b Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Tue, 4 Aug 2026 23:29:41 +0200
Subject: [PATCH 1/8] commit
---
.../java/fr/mrmicky/fastboard/FastBoard.java | 17 +++-
.../fr/mrmicky/fastboard/FastBoardBase.java | 99 +++++++++----------
.../fastboard/adventure/FastBoard.java | 8 +-
3 files changed, 68 insertions(+), 56 deletions(-)
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
index 5cc67f0..84e8ce3 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
@@ -133,10 +133,9 @@ protected void sendLineChange(int score) throws Throwable {
suffix = suffix.substring(0, Math.min(maxLength, suffix.length()));
}
- if (VersionType.V1_20_3.isCurrentAtLeast()) {
+ if (hasCustomScores()) {
sendModernScorePacket(score, ScoreboardAction.CHANGE);
} else {
- sendScorePacket(score, ScoreboardAction.CHANGE);
sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix);
}
}
@@ -170,4 +169,18 @@ protected String emptyLine() {
protected boolean hasLinesMaxLength() {
return !VersionType.V1_13.isCurrentAtLeast();
}
+
+ /**
+ * Returns whether scoreboard lines should use the score as text.
+ * By default, this is true only on Minecraft 1.20.3 and higher.
+ * Override this method for compatibility with plugins that provide multi-version support.
+ *
+ * If not overridden, with server above 1.20.3 and player under 1.20.3 will not see text lines.
+ *
+ * example: {@code return Via.getAPI().getPlayerVersion(getPlayer()) >= ProtocolVersion.v1_20_3.getVersion();}
+ * @return true should use new score as text, false for legacy team
+ */
+ protected boolean hasCustomScores() {
+ return VersionType.V1_20_3.isCurrentAtLeast();
+ }
}
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
index 4b98ee0..47ec3d5 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
@@ -107,9 +107,8 @@ public abstract class FastBoardBase {
Class> packetSbObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardObjective", "ClientboundSetObjectivePacket");
Class> packetSbDisplayObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardDisplayObjective", "ClientboundSetDisplayObjectivePacket");
Class> packetSbScoreClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardScore", "ClientboundSetScorePacket");
- Class> packetSbTeamClass = VersionType.V1_20_3.isCurrentAtLeast()
- ? null : FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam", "ClientboundSetPlayerTeamPacket");
- Class> sbTeamClass = VersionType.V1_17.isCurrentAtLeast() && !VersionType.V1_20_3.isCurrentAtLeast()
+ Class> packetSbTeamClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam", "ClientboundSetPlayerTeamPacket");
+ Class> sbTeamClass = VersionType.V1_17.isCurrentAtLeast()
? FastReflection.innerClass(packetSbTeamClass, innerClass -> !innerClass.isEnum()) : null;
Field playerConnectionField = Arrays.stream(entityPlayerClass.getFields())
.filter(field -> field.getType().isAssignableFrom(playerConnectionClass))
@@ -129,30 +128,29 @@ public abstract class FastBoardBase {
SEND_PACKET = lookup.unreflect(sendPacketMethod);
Class> scoreboardClass = FastReflection.nmsClass("world.scores", "Scoreboard");
- Class> playerTeamClass = VersionType.V1_20_3.isCurrentAtLeast() ?
- null : FastReflection.nmsClass("world.scores", "ScoreboardTeam", "PlayerTeam");
+ Class> playerTeamClass = FastReflection.nmsClass("world.scores", "ScoreboardTeam", "PlayerTeam");
Class> objectiveClass = FastReflection.nmsClass("world.scores", "ScoreboardObjective", "Objective");
Class> objectiveCriteriaClass = FastReflection.nmsClass("world.scores.criteria", "IScoreboardCriteria", "ObjectiveCriteria");
+ PLAYER_TEAM = lookup.unreflectConstructor(playerTeamClass.getConstructor(scoreboardClass, String.class));
Class> objectiveRenderTypeClass = FastReflection.nmsOptionalClass("world.scores.criteria", "IScoreboardCriteria$EnumScoreboardHealthDisplay", "ObjectiveCriteria$RenderType").orElse(null);
+ Optional> numberFormat = FastReflection.nmsOptionalClass("network.chat.numbers", "NumberFormat");
MethodHandle packetSbSetScore;
MethodHandle packetSbResetScore = null;
MethodHandle fixedFormatConstructor = null;
- MethodHandle packetSbSerializableTeam = null;
- MethodHandle packetSbTeam = null;
- MethodHandle playerTeam = null;
Object blankNumberFormat = null;
boolean scoreOptionalComponents = false;
- if (VersionType.V1_20_3.isCurrentAtLeast()) {
- Class> numberFormatClass = FastReflection.nmsClass("network.chat.numbers", "NumberFormat");
- OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass, CHAT_COMPONENT_CLASS, objectiveRenderTypeClass, boolean.class, numberFormatClass));
+ if (numberFormat.isPresent()) { // 1.20.3
+ OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass, CHAT_COMPONENT_CLASS, objectiveRenderTypeClass, boolean.class, numberFormat.get()));
+ PACKET_SB_OBJ = lookup.unreflectConstructor(packetSbObjClass.getConstructor(objectiveClass, int.class));
+ PACKET_SB_DISPLAY_OBJ = lookup.unreflectConstructor(packetSbDisplayObjClass.getConstructor(DISPLAY_SLOT_TYPE, objectiveClass));
Class> blankFormatClass = FastReflection.nmsClass("network.chat.numbers", "BlankFormat");
Class> fixedFormatClass = FastReflection.nmsClass("network.chat.numbers", "FixedFormat");
Class> resetScoreClass = FastReflection.nmsClass(gameProtocolPackage, "ClientboundResetScorePacket");
- MethodType scoreType = MethodType.methodType(void.class, String.class, String.class, int.class, CHAT_COMPONENT_CLASS, numberFormatClass);
+ MethodType scoreType = MethodType.methodType(void.class, String.class, String.class, int.class, CHAT_COMPONENT_CLASS, numberFormat.get());
MethodType scoreTypeOptional = MethodType.methodType(void.class, String.class, String.class, int.class, Optional.class, Optional.class);
MethodType removeScoreType = MethodType.methodType(void.class, String.class, String.class);
MethodType fixedFormatType = MethodType.methodType(void.class, CHAT_COMPONENT_CLASS);
@@ -165,51 +163,46 @@ public abstract class FastBoardBase {
scoreOptionalComponents = optionalScorePacket.isPresent();
packetSbResetScore = lookup.findConstructor(resetScoreClass, removeScoreType);
blankNumberFormat = blankField.isPresent() ? blankField.get().get(null) : null;
+ } else if (VersionType.V1_17.isCurrentAtLeast()) {
+ Class> enumSbAction = FastReflection.nmsClass("server", "ScoreboardServer$Action", "ServerScoreboard$Method");
+ MethodType scoreType = MethodType.methodType(void.class, enumSbAction, String.class, String.class, int.class);
+ packetSbSetScore = lookup.findConstructor(packetSbScoreClass, scoreType);
+ OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass, CHAT_COMPONENT_CLASS, objectiveRenderTypeClass));
+ PACKET_SB_OBJ = lookup.unreflectConstructor(packetSbObjClass.getConstructor(objectiveClass, int.class));
+ PACKET_SB_DISPLAY_OBJ = lookup.unreflectConstructor(packetSbDisplayObjClass.getConstructor(displaySlotEnum.orElse(int.class), objectiveClass));
} else {
- Constructor> packetSbTeamConstructor = sbTeamClass != null ? packetSbTeamClass.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class) : packetSbTeamClass.getDeclaredConstructor();
- packetSbTeamConstructor.setAccessible(true);
- packetSbTeam = lookup.unreflectConstructor(packetSbTeamConstructor);
- packetSbSerializableTeam = sbTeamClass != null ? lookup.unreflectConstructor(sbTeamClass.getConstructor(playerTeamClass)) : null;
-
- playerTeam = lookup.unreflectConstructor(playerTeamClass.getConstructor(scoreboardClass, String.class));
- if (VersionType.V1_17.isCurrentAtLeast()) {
- Class> enumSbAction = FastReflection.nmsClass("server", "ScoreboardServer$Action", "ServerScoreboard$Method");
- MethodType scoreType = MethodType.methodType(void.class, enumSbAction, String.class, String.class, int.class);
- packetSbSetScore = lookup.findConstructor(packetSbScoreClass, scoreType);
+ packetSbSetScore = lookup.findConstructor(packetSbScoreClass, MethodType.methodType(void.class));
+ if (VersionType.V1_13.isCurrentAtLeast()) {
OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass, CHAT_COMPONENT_CLASS, objectiveRenderTypeClass));
} else {
- packetSbSetScore = lookup.findConstructor(packetSbScoreClass, MethodType.methodType(void.class));
- if (VersionType.V1_13.isCurrentAtLeast()) {
- OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass, CHAT_COMPONENT_CLASS, objectiveRenderTypeClass));
- } else {
- OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass));
- }
- }
-
- for (Class> clazz : Arrays.asList(packetSbScoreClass, packetSbTeamClass, sbTeamClass, playerTeamClass, objectiveClass)) {
- if (clazz == null) {
- continue;
- }
- Field[] fields = Arrays.stream(clazz.getDeclaredFields())
- .filter(field -> !Modifier.isStatic(field.getModifiers()))
- .toArray(Field[]::new);
- for (Field field : fields) {
- field.setAccessible(true);
- }
- PACKETS.put(clazz, fields);
+ OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass));
}
+ PACKET_SB_OBJ = lookup.unreflectConstructor(packetSbObjClass.getConstructor(objectiveClass, int.class));
+ PACKET_SB_DISPLAY_OBJ = lookup.unreflectConstructor(packetSbDisplayObjClass.getConstructor(int.class, objectiveClass));
}
- PACKET_SB_OBJ = lookup.unreflectConstructor(packetSbObjClass.getConstructor(objectiveClass, int.class));
- PACKET_SB_DISPLAY_OBJ = lookup.unreflectConstructor(packetSbDisplayObjClass.getConstructor(DISPLAY_SLOT_TYPE, objectiveClass));
PACKET_SB_SET_SCORE = packetSbSetScore;
PACKET_SB_RESET_SCORE = packetSbResetScore;
- PACKET_SB_TEAM = packetSbTeam;
- PACKET_SB_SERIALIZABLE_TEAM = packetSbSerializableTeam;
+ Constructor> packetSbTeamConstructor = sbTeamClass != null ? packetSbTeamClass.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class) : packetSbTeamClass.getDeclaredConstructor();
+ packetSbTeamConstructor.setAccessible(true);
+ PACKET_SB_TEAM = lookup.unreflectConstructor(packetSbTeamConstructor);
+ PACKET_SB_SERIALIZABLE_TEAM = sbTeamClass != null ? lookup.unreflectConstructor(sbTeamClass.getConstructor(playerTeamClass)) : null;
FIXED_NUMBER_FORMAT = fixedFormatConstructor;
BLANK_NUMBER_FORMAT = blankNumberFormat;
SCORE_OPTIONAL_COMPONENTS = scoreOptionalComponents;
- PLAYER_TEAM = playerTeam;
+
+ for (Class> clazz : Arrays.asList(packetSbScoreClass, packetSbTeamClass, sbTeamClass, playerTeamClass, objectiveClass)) {
+ if (clazz == null) {
+ continue;
+ }
+ Field[] fields = Arrays.stream(clazz.getDeclaredFields())
+ .filter(field -> !Modifier.isStatic(field.getModifiers()))
+ .toArray(Field[]::new);
+ for (Field field : fields) {
+ field.setAccessible(true);
+ }
+ PACKETS.put(clazz, fields);
+ }
if (VersionType.V1_8.isCurrentAtLeast()) {
String enumSbActionClass = VersionType.V1_13.isCurrentAtLeast()
@@ -453,13 +446,13 @@ public synchronized void updateLines(Collection lines, Collection scores)
if (oldLines.size() > linesSize) {
for (int i = oldLinesCopy.size(); i > linesSize; i--) {
- if (!VersionType.V1_20_3.isCurrentAtLeast()) {
+ if (!hasCustomScores()) {
sendTeamPacket(i - 1, TeamMode.REMOVE);
}
sendScorePacket(i - 1, ScoreboardAction.REMOVE);
oldLines.remove(0);
}
- } else if (!VersionType.V1_20_3.isCurrentAtLeast()) {
+ } else if (!hasCustomScores()) {
for (int i = oldLinesCopy.size(); i < linesSize; i++) {
sendScorePacket(i, ScoreboardAction.CHANGE);
sendTeamPacket(i, TeamMode.CREATE, null, null);
@@ -470,7 +463,7 @@ public synchronized void updateLines(Collection lines, Collection scores)
for (int i = 0; i < linesSize; i++) {
boolean isNewTextDifferentFromOld = !Objects.equals(getLineByScore(oldLines, i), getLineByScore(i));
boolean isNewFormatDifferentFromOld = !Objects.equals(getLineByScore(oldScores, i), getLineByScore(this.scores, i));
- if (VersionType.V1_20_3.isCurrentAtLeast() && (isNewTextDifferentFromOld || isNewFormatDifferentFromOld)) {
+ if (hasCustomScores() && (isNewTextDifferentFromOld || isNewFormatDifferentFromOld)) {
sendModernScorePacket(i, ScoreboardAction.CHANGE);
} else if (isNewTextDifferentFromOld) {
sendLineChange(i);
@@ -496,7 +489,7 @@ public synchronized void updateScore(int line, T score) {
this.scores.set(line, score);
try {
- if (VersionType.V1_20_3.isCurrentAtLeast()) {
+ if (hasCustomScores()) {
sendModernScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE);
}
} catch (Throwable e) {
@@ -551,7 +544,7 @@ public synchronized void updateScores(Collection scores) {
this.scores.set(i, newScores.get(i));
try {
- if (VersionType.V1_20_3.isCurrentAtLeast()) {
+ if (hasCustomScores()) {
sendModernScorePacket(getScoreByLine(i), ScoreboardAction.CHANGE);
}
} catch (Throwable e) {
@@ -607,7 +600,7 @@ public synchronized void delete() {
try {
for (int i = 0; i < this.lines.size(); i++) {
- if (VersionType.V1_20_3.isCurrentAtLeast()) {
+ if (hasCustomScores()) {
sendScorePacket(i, ScoreboardAction.REMOVE);
} else {
sendTeamPacket(i, TeamMode.REMOVE);
@@ -635,6 +628,8 @@ public synchronized void delete() {
protected abstract T emptyLine();
+ protected abstract boolean hasCustomScores();
+
private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {
if (line < 0) {
throw new IllegalArgumentException("Line number must be positive");
diff --git a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
index 6717ba7..36ee97a 100644
--- a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
@@ -81,10 +81,9 @@ public FastBoard(Player player) {
protected void sendLineChange(int score) throws Throwable {
Component line = getLineByScore(score);
- if (VersionType.V1_20_3.isCurrentAtLeast()) {
+ if (hasCustomScores()) {
sendModernScorePacket(score, ScoreboardAction.CHANGE);
} else {
- sendScorePacket(score, ScoreboardAction.CHANGE);
sendTeamPacket(score, TeamMode.UPDATE, line, null);
}
}
@@ -115,4 +114,9 @@ protected String serializeLine(Component value) {
protected Component emptyLine() {
return Component.empty();
}
+
+ @Override
+ protected boolean hasCustomScores() {
+ return VersionType.V1_20_3.isCurrentAtLeast();
+ }
}
From af607ff09d5c06fe12fd8e32eb45e17e8603486e Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Tue, 4 Aug 2026 23:43:07 +0200
Subject: [PATCH 2/8] version
---
README.md | 4 ++--
pom.xml | 2 +-
src/main/java/fr/mrmicky/fastboard/FastBoardBase.java | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 5033e94..23f4161 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ Lightweight packet-based scoreboard API for Bukkit plugins, compatible with all
fr.mrmicky
fastboard
- 2.2.1
+ 2.2.2
```
@@ -81,7 +81,7 @@ repositories {
}
dependencies {
- implementation 'fr.mrmicky:fastboard:2.2.1'
+ implementation 'fr.mrmicky:fastboard:2.2.2'
}
shadowJar {
diff --git a/pom.xml b/pom.xml
index b0fb214..6695de4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
fr.mrmicky
fastboard
- 2.2.1
+ 2.2.2
FastBoard
Lightweight packet-based scoreboard API for Bukkit plugins.
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
index 47ec3d5..00ee43d 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
@@ -44,7 +44,7 @@
* The project is on GitHub.
*
* @author MrMicky
- * @version 2.2.1
+ * @version 2.2.2
*/
public abstract class FastBoardBase {
From 924cc5f8442f1b9fff61a9921f63f61f4d835c53 Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Tue, 4 Aug 2026 23:50:17 +0200
Subject: [PATCH 3/8] java docs
---
src/main/java/fr/mrmicky/fastboard/FastBoard.java | 9 +--------
src/main/java/fr/mrmicky/fastboard/FastBoardBase.java | 10 ++++++++++
.../java/fr/mrmicky/fastboard/adventure/FastBoard.java | 3 +++
3 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
index 84e8ce3..5992e65 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
@@ -171,14 +171,7 @@ protected boolean hasLinesMaxLength() {
}
/**
- * Returns whether scoreboard lines should use the score as text.
- * By default, this is true only on Minecraft 1.20.3 and higher.
- * Override this method for compatibility with plugins that provide multi-version support.
- *
- * If not overridden, with server above 1.20.3 and player under 1.20.3 will not see text lines.
- *
- * example: {@code return Via.getAPI().getPlayerVersion(getPlayer()) >= ProtocolVersion.v1_20_3.getVersion();}
- * @return true should use new score as text, false for legacy team
+ * {@inheritDoc}
*/
protected boolean hasCustomScores() {
return VersionType.V1_20_3.isCurrentAtLeast();
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
index 00ee43d..7b65547 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
@@ -628,6 +628,16 @@ public synchronized void delete() {
protected abstract T emptyLine();
+ /**
+ * Returns whether scoreboard lines should use the score as text.
+ * By default, this is true only on Minecraft 1.20.3 and higher.
+ * Override this method for compatibility with plugins that provide multi-version support.
+ *
+ * If not overridden, with server above 1.20.3 and player under 1.20.3 will not see text lines.
+ *
+ * example: {@code return Via.getAPI().getPlayerVersion(getPlayer()) >= ProtocolVersion.v1_20_3.getVersion();}
+ * @return true should use new score as text, false for legacy team
+ */
protected abstract boolean hasCustomScores();
private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {
diff --git a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
index 36ee97a..9fc66f7 100644
--- a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
@@ -115,6 +115,9 @@ protected Component emptyLine() {
return Component.empty();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
protected boolean hasCustomScores() {
return VersionType.V1_20_3.isCurrentAtLeast();
From e7e46ac07f5da6e26a8b95bb8f6295476f6c6239 Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Tue, 4 Aug 2026 23:54:23 +0200
Subject: [PATCH 4/8] simplify
---
src/main/java/fr/mrmicky/fastboard/FastBoard.java | 7 -------
src/main/java/fr/mrmicky/fastboard/FastBoardBase.java | 4 +++-
.../java/fr/mrmicky/fastboard/adventure/FastBoard.java | 8 --------
3 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
index 5992e65..1e0d698 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
@@ -169,11 +169,4 @@ protected String emptyLine() {
protected boolean hasLinesMaxLength() {
return !VersionType.V1_13.isCurrentAtLeast();
}
-
- /**
- * {@inheritDoc}
- */
- protected boolean hasCustomScores() {
- return VersionType.V1_20_3.isCurrentAtLeast();
- }
}
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
index 7b65547..99cd975 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
@@ -638,7 +638,9 @@ public synchronized void delete() {
* example: {@code return Via.getAPI().getPlayerVersion(getPlayer()) >= ProtocolVersion.v1_20_3.getVersion();}
* @return true should use new score as text, false for legacy team
*/
- protected abstract boolean hasCustomScores();
+ protected boolean hasCustomScores() {
+ return VersionType.V1_20_3.isCurrentAtLeast();
+ }
private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {
if (line < 0) {
diff --git a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
index 9fc66f7..e7cb577 100644
--- a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
@@ -114,12 +114,4 @@ protected String serializeLine(Component value) {
protected Component emptyLine() {
return Component.empty();
}
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected boolean hasCustomScores() {
- return VersionType.V1_20_3.isCurrentAtLeast();
- }
}
From 8b923d4f458aade5f4bc34ab8b110e98da46ca2b Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Mon, 10 Aug 2026 22:46:01 +0200
Subject: [PATCH 5/8] readme
---
README.md | 30 +++++++++++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 23f4161..21b897c 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,9 @@
Lightweight packet-based scoreboard API for Bukkit plugins, compatible with all Minecraft versions starting with 1.7.10.
+> [!WARNING]
+> If you're using ViaBackwards, please read the [ViaBackwards compatibility](#viabackwards-compatibility) section.
+
> [!IMPORTANT]
> To use FastBoard on a 1.8 server, the server must be on 1.8.8.
@@ -13,7 +16,7 @@ Lightweight packet-based scoreboard API for Bukkit plugins, compatible with all
* No flickering (without using a buffer)
* Compatible with all Minecraft versions starting with 1.7.10
-* Small (around 750 lines of code with the Javadoc) and no dependencies
+* Small and no dependencies
* Easy to use
* 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
* Everything is at the packet level, so it works with other plugins using scoreboard and/or teams
@@ -22,6 +25,7 @@ Lightweight packet-based scoreboard API for Bukkit plugins, compatible with all
* No character limit on 1.13 and higher
* [RGB HEX colors support](#rgb-colors) on 1.16 and higher
* [Custom number formatting](#custom-number-formatting) (including blank) for scores on 1.20.3 and higher
+* [Custom score compatibility](#custom-score-compatibility) for multi-version servers
* [Adventure components support](#adventure-support)
* Support for both Spigot and Mojang mappings
@@ -209,7 +213,9 @@ Passing a `null` value as a score will result in a reset to the default blank fo
## ViaBackwards compatibility
-When using ViaBackwards on a post-1.13 server with pre-1.13 clients, older clients
+### On server versions above 1.13 with clients pre 1.13
+
+On a post-1.13 server with pre-1.13 clients, older clients
may receive incomplete lines. To solve this problem, you can override the `hasLinesMaxLength()` method and return `true` for older clients.
For example, using the ViaVersion API:
```java
@@ -218,5 +224,23 @@ FastBoard board = new FastBoard(player) {
public boolean hasLinesMaxLength() {
return Via.getAPI().getPlayerVersion(getPlayer()) < ProtocolVersion.v1_13.getVersion(); // or just 'return true;'
}
-});
+};
+```
+
+### On server versions above 1.20.3 with clients pre 1.20.3
+
+
+On a post-1.20.3 server with pre-1.20.3 clients, older clients will receive empty lines.
+To solve this problem, you can override the `hasCustomScores()` method and return `false` for older clients.
+For example, using the ViaVersion API:
+
+```java
+FastBoard board = new FastBoard(player) {
+ @Override
+ public boolean hasCustomScores() {
+ return Via.getAPI().getPlayerVersion(getPlayer()) >= ProtocolVersion.v1_20_3.getVersion();
+ }
+};
```
+
+Note: if you don't want to use the score system, you can force `hasCustomScores()` to return `false` for all players, and FastBoard will always use the legacy team-based line format, which is compatible with all versions.
From 2e4b0c86ae58799126e720012e593c615c6d2748 Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Wed, 12 Aug 2026 23:58:30 +0200
Subject: [PATCH 6/8] safeguards
---
src/main/java/fr/mrmicky/fastboard/FastBoard.java | 2 +-
src/main/java/fr/mrmicky/fastboard/FastBoardBase.java | 8 ++++----
.../java/fr/mrmicky/fastboard/adventure/FastBoard.java | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
index 1e0d698..31c25b2 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoard.java
@@ -133,7 +133,7 @@ protected void sendLineChange(int score) throws Throwable {
suffix = suffix.substring(0, Math.min(maxLength, suffix.length()));
}
- if (hasCustomScores()) {
+ if (VersionType.V1_20_3.isCurrentAtLeast() && hasCustomScores()) {
sendModernScorePacket(score, ScoreboardAction.CHANGE);
} else {
sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix);
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
index 99cd975..5142ac1 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
@@ -463,7 +463,7 @@ public synchronized void updateLines(Collection lines, Collection scores)
for (int i = 0; i < linesSize; i++) {
boolean isNewTextDifferentFromOld = !Objects.equals(getLineByScore(oldLines, i), getLineByScore(i));
boolean isNewFormatDifferentFromOld = !Objects.equals(getLineByScore(oldScores, i), getLineByScore(this.scores, i));
- if (hasCustomScores() && (isNewTextDifferentFromOld || isNewFormatDifferentFromOld)) {
+ if (VersionType.V1_20_3.isCurrentAtLeast() && hasCustomScores() && (isNewTextDifferentFromOld || isNewFormatDifferentFromOld)) {
sendModernScorePacket(i, ScoreboardAction.CHANGE);
} else if (isNewTextDifferentFromOld) {
sendLineChange(i);
@@ -489,7 +489,7 @@ public synchronized void updateScore(int line, T score) {
this.scores.set(line, score);
try {
- if (hasCustomScores()) {
+ if (VersionType.V1_20_3.isCurrentAtLeast() && hasCustomScores()) {
sendModernScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE);
}
} catch (Throwable e) {
@@ -544,7 +544,7 @@ public synchronized void updateScores(Collection scores) {
this.scores.set(i, newScores.get(i));
try {
- if (hasCustomScores()) {
+ if (VersionType.V1_20_3.isCurrentAtLeast() && hasCustomScores()) {
sendModernScorePacket(getScoreByLine(i), ScoreboardAction.CHANGE);
}
} catch (Throwable e) {
@@ -600,7 +600,7 @@ public synchronized void delete() {
try {
for (int i = 0; i < this.lines.size(); i++) {
- if (hasCustomScores()) {
+ if (VersionType.V1_20_3.isCurrentAtLeast() && hasCustomScores()) {
sendScorePacket(i, ScoreboardAction.REMOVE);
} else {
sendTeamPacket(i, TeamMode.REMOVE);
diff --git a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
index e7cb577..c63a5e5 100644
--- a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
+++ b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java
@@ -81,7 +81,7 @@ public FastBoard(Player player) {
protected void sendLineChange(int score) throws Throwable {
Component line = getLineByScore(score);
- if (hasCustomScores()) {
+ if (VersionType.V1_20_3.isCurrentAtLeast() && hasCustomScores()) {
sendModernScorePacket(score, ScoreboardAction.CHANGE);
} else {
sendTeamPacket(score, TeamMode.UPDATE, line, null);
From 84229bf8ec8d936bfc9007c522b88c0822d7782f Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Thu, 13 Aug 2026 00:02:34 +0200
Subject: [PATCH 7/8] typo
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index 21b897c..79ac571 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,6 @@ Lightweight packet-based scoreboard API for Bukkit plugins, compatible with all
* No character limit on 1.13 and higher
* [RGB HEX colors support](#rgb-colors) on 1.16 and higher
* [Custom number formatting](#custom-number-formatting) (including blank) for scores on 1.20.3 and higher
-* [Custom score compatibility](#custom-score-compatibility) for multi-version servers
* [Adventure components support](#adventure-support)
* Support for both Spigot and Mojang mappings
From e1bc34d11d20bb6de03c75854df69d209f56a12a Mon Sep 17 00:00:00 2001
From: Dominilk01 <108392199+Dominilk01@users.noreply.github.com>
Date: Sat, 15 Aug 2026 20:46:16 +0200
Subject: [PATCH 8/8] true
---
src/main/java/fr/mrmicky/fastboard/FastBoardBase.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
index 5142ac1..c71f0f0 100644
--- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
+++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java
@@ -639,7 +639,7 @@ public synchronized void delete() {
* @return true should use new score as text, false for legacy team
*/
protected boolean hasCustomScores() {
- return VersionType.V1_20_3.isCurrentAtLeast();
+ return true;
}
private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {