diff --git a/pom.xml b/pom.xml index 0b4ef14..3f8909f 100644 --- a/pom.xml +++ b/pom.xml @@ -52,13 +52,13 @@ net.kyori adventure-api - 5.1.1 + 5.2.0 provided net.kyori adventure-text-serializer-legacy - 5.1.1 + 5.2.0 provided diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/FastBoard.java index 53b9b04..5cc67f0 100644 --- a/src/main/java/fr/mrmicky/fastboard/FastBoard.java +++ b/src/main/java/fr/mrmicky/fastboard/FastBoard.java @@ -66,7 +66,7 @@ public FastBoard(Player player) { public void updateTitle(String title) { Objects.requireNonNull(title, "title"); - if (!VersionType.V1_13.isHigherOrEqual() && title.length() > 32) { + if (!VersionType.V1_13.isCurrentAtLeast() && title.length() > 32) { throw new IllegalArgumentException("Title is longer than 32 chars"); } @@ -82,7 +82,7 @@ public void updateTitle(String title) { public void updateLines(String... lines) { Objects.requireNonNull(lines, "lines"); - if (!VersionType.V1_13.isHigherOrEqual()) { + if (!VersionType.V1_13.isCurrentAtLeast()) { int lineCount = 0; for (String s : lines) { if (s != null && s.length() > 30) { @@ -95,6 +95,9 @@ public void updateLines(String... lines) { super.updateLines(lines); } + /** + * {@inheritDoc} + */ @Override protected void sendLineChange(int score) throws Throwable { int maxLength = hasLinesMaxLength() ? 16 : 1024; @@ -130,7 +133,12 @@ protected void sendLineChange(int score) throws Throwable { suffix = suffix.substring(0, Math.min(maxLength, suffix.length())); } - sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix); + if (VersionType.V1_20_3.isCurrentAtLeast()) { + sendModernScorePacket(score, ScoreboardAction.CHANGE); + } else { + sendScorePacket(score, ScoreboardAction.CHANGE); + sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix); + } } @Override @@ -160,6 +168,6 @@ protected String emptyLine() { * @return true if scoreboard lines are limited by the legacy prefix/suffix length */ protected boolean hasLinesMaxLength() { - return !VersionType.V1_13.isHigherOrEqual(); + return !VersionType.V1_13.isCurrentAtLeast(); } } diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java index e2b215d..c8a8a85 100644 --- a/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java +++ b/src/main/java/fr/mrmicky/fastboard/FastBoardBase.java @@ -85,7 +85,9 @@ public abstract class FastBoardBase { try { MethodHandles.Lookup lookup = MethodHandles.lookup(); - if (FastReflection.isRepackaged()) { + if (FastReflection.nmsOptionalClass("network.chat.numbers", "NumberFormat").isPresent()) { + VERSION_TYPE = VersionType.V1_20_3; + } else if (FastReflection.isRepackaged()) { VERSION_TYPE = VersionType.V1_17; } else if (FastReflection.nmsOptionalClass(null, "ScoreboardServer$Action").isPresent() || FastReflection.nmsOptionalClass(null, "ServerScoreboard$Method").isPresent()) { @@ -105,8 +107,9 @@ 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 = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam", "ClientboundSetPlayerTeamPacket"); - Class sbTeamClass = VersionType.V1_17.isHigherOrEqual() + Class packetSbTeamClass = VersionType.V1_20_3.isCurrentAtLeast() + ? null : FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam", "ClientboundSetPlayerTeamPacket"); + Class sbTeamClass = VersionType.V1_17.isCurrentAtLeast() && !VersionType.V1_20_3.isCurrentAtLeast() ? FastReflection.innerClass(packetSbTeamClass, innerClass -> !innerClass.isEnum()) : null; Field playerConnectionField = Arrays.stream(entityPlayerClass.getFields()) .filter(field -> field.getType().isAssignableFrom(playerConnectionClass)) @@ -126,29 +129,30 @@ public abstract class FastBoardBase { SEND_PACKET = lookup.unreflect(sendPacketMethod); Class scoreboardClass = FastReflection.nmsClass("world.scores", "Scoreboard"); - Class playerTeamClass = FastReflection.nmsClass("world.scores", "ScoreboardTeam", "PlayerTeam"); + Class playerTeamClass = VersionType.V1_20_3.isCurrentAtLeast() ? + null : 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 (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)); + 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)); 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, numberFormat.get()); + MethodType scoreType = MethodType.methodType(void.class, String.class, String.class, int.class, CHAT_COMPONENT_CLASS, numberFormatClass); 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); @@ -161,49 +165,54 @@ 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.isHigherOrEqual()) { - 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(int.class, objectiveClass)); } else { - packetSbSetScore = lookup.findConstructor(packetSbScoreClass, MethodType.methodType(void.class)); - if (VersionType.V1_13.isHigherOrEqual()) { + 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); OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass, CHAT_COMPONENT_CLASS, objectiveRenderTypeClass)); } else { - OBJECTIVE = lookup.unreflectConstructor(objectiveClass.getConstructor(scoreboardClass, String.class, objectiveCriteriaClass)); + 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); } - 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; - 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; + PACKET_SB_TEAM = packetSbTeam; + PACKET_SB_SERIALIZABLE_TEAM = packetSbSerializableTeam; 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.isHigherOrEqual()) { - String enumSbActionClass = VersionType.V1_13.isHigherOrEqual() + if (VersionType.V1_8.isCurrentAtLeast()) { + String enumSbActionClass = VersionType.V1_13.isCurrentAtLeast() ? "ScoreboardServer$Action" : "PacketPlayOutScoreboardScore$EnumScoreboardAction"; ENUM_SB_HEALTH_DISPLAY = FastReflection.nmsClass("world.scores.criteria", "IScoreboardCriteria$EnumScoreboardHealthDisplay", "ObjectiveCriteria$RenderType"); @@ -218,7 +227,7 @@ public abstract class FastBoardBase { ENUM_SB_ACTION_CHANGE = null; ENUM_SB_ACTION_REMOVE = null; } - if (VersionType.V1_13.isHigherOrEqual()) { + if (VersionType.V1_13.isCurrentAtLeast()) { DUMMY_SCOREBOARD_CRITERIA = null; } else { DUMMY_SCOREBOARD_CRITERIA = FastReflection.nmsClass("world.scores.criteria", "ScoreboardBaseCriteria").getConstructor(String.class).newInstance("dummy"); @@ -347,11 +356,6 @@ public synchronized void updateLine(int line, T score, T scoreText) { this.scores.set(line, scoreText); sendLineChange(getScoreByLine(line)); - - if (customScoresSupported()) { - sendScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE); - } - return; } @@ -449,11 +453,13 @@ public synchronized void updateLines(Collection lines, Collection scores) if (oldLines.size() > linesSize) { for (int i = oldLinesCopy.size(); i > linesSize; i--) { - sendTeamPacket(i - 1, TeamMode.REMOVE); + if (!VersionType.V1_20_3.isCurrentAtLeast()) { + sendTeamPacket(i - 1, TeamMode.REMOVE); + } sendScorePacket(i - 1, ScoreboardAction.REMOVE); oldLines.remove(0); } - } else { + } else if (!VersionType.V1_20_3.isCurrentAtLeast()) { for (int i = oldLinesCopy.size(); i < linesSize; i++) { sendScorePacket(i, ScoreboardAction.CHANGE); sendTeamPacket(i, TeamMode.CREATE, null, null); @@ -462,11 +468,14 @@ public synchronized void updateLines(Collection lines, Collection scores) } for (int i = 0; i < linesSize; i++) { - if (!Objects.equals(getLineByScore(oldLines, i), getLineByScore(i))) { - sendLineChange(i); - } - if (!Objects.equals(getLineByScore(oldScores, i), getLineByScore(this.scores, i))) { - sendScorePacket(i, ScoreboardAction.CHANGE); + 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)) { + sendModernScorePacket(i, ScoreboardAction.CHANGE); + } else { + if (isNewTextDifferentFromOld) { + sendLineChange(i); + } } } } catch (Throwable t) { @@ -489,8 +498,8 @@ public synchronized void updateScore(int line, T score) { this.scores.set(line, score); try { - if (customScoresSupported()) { - sendScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE); + if (VersionType.V1_20_3.isCurrentAtLeast()) { + sendModernScorePacket(getScoreByLine(line), ScoreboardAction.CHANGE); } } catch (Throwable e) { throw new RuntimeException("Unable to update line score", e); @@ -544,8 +553,8 @@ public synchronized void updateScores(Collection scores) { this.scores.set(i, newScores.get(i)); try { - if (customScoresSupported()) { - sendScorePacket(getScoreByLine(i), ScoreboardAction.CHANGE); + if (VersionType.V1_20_3.isCurrentAtLeast()) { + sendModernScorePacket(getScoreByLine(i), ScoreboardAction.CHANGE); } } catch (Throwable e) { throw new RuntimeException("Unable to update scores", e); @@ -580,15 +589,6 @@ public boolean isDeleted() { return this.deleted; } - /** - * Returns whether the server supports custom scoreboard scores (1.20.3+ servers only). - * - * @return true if the server supports custom scores - */ - public boolean customScoresSupported() { - return BLANK_NUMBER_FORMAT != null; - } - /** * Returns the scoreboard size (the number of lines). * @@ -609,7 +609,11 @@ public synchronized void delete() { try { for (int i = 0; i < this.lines.size(); i++) { - sendTeamPacket(i, TeamMode.REMOVE); + if (VersionType.V1_20_3.isCurrentAtLeast()) { + sendScorePacket(i, ScoreboardAction.REMOVE); + } else { + sendTeamPacket(i, TeamMode.REMOVE); + } } sendObjectivePacket(ObjectiveMode.REMOVE); @@ -620,6 +624,11 @@ public synchronized void delete() { this.deleted = true; } + /** + * Sends a line update packet for the specified internal score. + * + * @param score the position to update + */ protected abstract void sendLineChange(int score) throws Throwable; protected abstract Object toMinecraftComponent(T value) throws Throwable; @@ -666,15 +675,7 @@ protected Object sendObjectivePacket(ObjectiveMode mode) throws Throwable { false, // Auto-update, unused null // Number format ); - } else if (VersionType.V1_17.isHigherOrEqual()) { - objective = OBJECTIVE.invoke( - null, // Scoreboard, unused - this.id, // Objective name - null, // Criteria, unused - toMinecraftComponent(this.title), // Display name - ENUM_SB_HEALTH_DISPLAY_INTEGER // Render type - ); - } else if (VersionType.V1_13.isHigherOrEqual()) { + } else if (VersionType.V1_13.isCurrentAtLeast()) { objective = OBJECTIVE.invoke( null, // Scoreboard, unused this.id, // Objective name @@ -705,7 +706,7 @@ protected void sendDisplayObjectivePacket(Object objective) throws Throwable { } protected void sendScorePacket(int score, ScoreboardAction action) throws Throwable { - if (VersionType.V1_17.isHigherOrEqual()) { + if (VersionType.V1_17.isCurrentAtLeast()) { sendModernScorePacket(score, action); return; } @@ -714,7 +715,7 @@ protected void sendScorePacket(int score, ScoreboardAction action) throws Throwa setField(packet, String.class, COLOR_CODES[score], 0); // Player Name - if (VersionType.V1_8.isHigherOrEqual()) { + if (VersionType.V1_8.isCurrentAtLeast()) { Object enumAction = action == ScoreboardAction.REMOVE ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE; setField(packet, ENUM_SB_ACTION, enumAction); @@ -730,7 +731,7 @@ protected void sendScorePacket(int score, ScoreboardAction action) throws Throwa sendPacket(packet); } - private void sendModernScorePacket(int score, ScoreboardAction action) throws Throwable { + protected void sendModernScorePacket(int score, ScoreboardAction action) throws Throwable { String objName = COLOR_CODES[score]; Object enumAction = action == ScoreboardAction.REMOVE ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE; @@ -745,13 +746,14 @@ private void sendModernScorePacket(int score, ScoreboardAction action) throws Th return; } - T scoreFormat = getLineByScore(this.scores, score); - Object format = scoreFormat != null - ? FIXED_NUMBER_FORMAT.invoke(toMinecraftComponent(scoreFormat)) + Object text = toMinecraftComponent(getLineByScore(score)); + T rawFormat = getLineByScore(this.scores, score); + Object format = rawFormat != null + ? FIXED_NUMBER_FORMAT.invoke(toMinecraftComponent(rawFormat)) : BLANK_NUMBER_FORMAT; Object scorePacket = SCORE_OPTIONAL_COMPONENTS - ? PACKET_SB_SET_SCORE.invoke(objName, this.id, score, Optional.empty(), Optional.of(format)) - : PACKET_SB_SET_SCORE.invoke(objName, this.id, score, null, format); + ? PACKET_SB_SET_SCORE.invoke(objName, this.id, score, Optional.of(text), Optional.of(format)) + : PACKET_SB_SET_SCORE.invoke(objName, this.id, score, text, format); sendPacket(scorePacket); } @@ -768,7 +770,7 @@ protected void sendTeamPacket(int score, TeamMode mode, T prefix, T suffix) Object packet; if (mode == TeamMode.REMOVE) { - if (VersionType.V1_17.isHigherOrEqual()) { + if (VersionType.V1_17.isCurrentAtLeast()) { packet = PACKET_SB_TEAM.invoke( this.id + ':' + score, // Team name mode.ordinal(), // Update mode @@ -784,7 +786,7 @@ protected void sendTeamPacket(int score, TeamMode mode, T prefix, T suffix) return; } - if (VersionType.V1_17.isHigherOrEqual()) { + if (VersionType.V1_17.isCurrentAtLeast()) { Object team = PLAYER_TEAM.invoke(null, this.id + ':' + score); setComponentField(team, null, 1); // Display name setComponentField(team, prefix, 2); // Prefix @@ -840,7 +842,7 @@ private void setField(Object packet, Class fieldType, Object value, int count } private void setComponentField(Object packet, T value, int count) throws Throwable { - if (!VersionType.V1_13.isHigherOrEqual()) { + if (!VersionType.V1_13.isCurrentAtLeast()) { String line = value != null ? serializeLine(value) : ""; setField(packet, String.class, line, count); return; @@ -866,10 +868,10 @@ public enum ScoreboardAction { CHANGE, REMOVE } - enum VersionType { - V1_7, V1_8, V1_13, V1_17; + public enum VersionType { + V1_7, V1_8, V1_13, V1_17, V1_20_3; - public boolean isHigherOrEqual() { + public boolean isCurrentAtLeast() { return VERSION_TYPE.ordinal() >= ordinal(); } } diff --git a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java index 77b3b12..6717ba7 100644 --- a/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java +++ b/src/main/java/fr/mrmicky/fastboard/adventure/FastBoard.java @@ -75,15 +75,18 @@ public FastBoard(Player player) { } /** - * Sends a line update packet for the specified internal score. - * - * @param score the internal score of the line to update + * {@inheritDoc} */ @Override protected void sendLineChange(int score) throws Throwable { Component line = getLineByScore(score); - sendTeamPacket(score, FastBoardBase.TeamMode.UPDATE, line, null); + if (VersionType.V1_20_3.isCurrentAtLeast()) { + sendModernScorePacket(score, ScoreboardAction.CHANGE); + } else { + sendScorePacket(score, ScoreboardAction.CHANGE); + sendTeamPacket(score, TeamMode.UPDATE, line, null); + } } @Override