diff --git a/plugin.yml b/plugin.yml
index 403133f..b146766 100644
--- a/plugin.yml
+++ b/plugin.yml
@@ -1,5 +1,5 @@
main: mc.ajneb97.MineChess
-version: 1.14.1
+version: 1.16.1
name: MineChess
api-version: 1.13
softdepend: [Multiverse-Core,HolographicDisplays,PlaceholderAPI]
diff --git a/src/mc/ajneb97/juego/Tablero.java b/src/mc/ajneb97/juego/Tablero.java
index 7c1b9f2..059faaf 100644
--- a/src/mc/ajneb97/juego/Tablero.java
+++ b/src/mc/ajneb97/juego/Tablero.java
@@ -21,7 +21,7 @@ public class Tablero {
if(color.equals("b")) {
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
m = Material.valueOf("WHITE_WOOL");
}else {
m = Material.WOOL;
@@ -39,7 +39,7 @@ public class Tablero {
}else {
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
m = Material.valueOf("GRAY_WOOL");
l.getBlock().setType(m);
l.clone().add(1,0,0).getBlock().setType(m);
diff --git a/src/mc/ajneb97/lib/actionbarapi/ActionBarAPI.java b/src/mc/ajneb97/lib/actionbarapi/ActionBarAPI.java
index 9086801..31d85dc 100644
--- a/src/mc/ajneb97/lib/actionbarapi/ActionBarAPI.java
+++ b/src/mc/ajneb97/lib/actionbarapi/ActionBarAPI.java
@@ -17,7 +17,7 @@ public class ActionBarAPI
public static void sendActionBar(Player player, String message)
{
if(Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
TextComponent text_component = new TextComponent(message);
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, text_component);
return;
diff --git a/src/mc/ajneb97/lib/fastboard/FastBoard.java b/src/mc/ajneb97/lib/fastboard/FastBoard.java
index 05e2630..30f2fe0 100644
--- a/src/mc/ajneb97/lib/fastboard/FastBoard.java
+++ b/src/mc/ajneb97/lib/fastboard/FastBoard.java
@@ -1,3 +1,26 @@
+/*
+ * This file is part of FastBoard, licensed under the MIT License.
+ *
+ * Copyright (c) 2019-2023 MrMicky
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
package mc.ajneb97.lib.fastboard;
import org.bukkit.ChatColor;
@@ -5,294 +28,55 @@ import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ThreadLocalRandom;
/**
- * Lightweight packet-based scoreboard API for Bukkit plugins.
- * It can be safely used asynchronously as everything is at packet level.
- *
- * The project is on GitHub.
- *
- * @author MrMicky
- * @version 1.2.0-SNAPSHOT
+ * {@inheritDoc}
*/
-public class FastBoard {
+public class FastBoard extends FastBoardBase {
- private static final Map, Field[]> PACKETS = new HashMap<>(8);
- private static final String[] COLOR_CODES = Arrays.stream(ChatColor.values())
- .map(Object::toString)
- .toArray(String[]::new);
- private static final VersionType VERSION_TYPE;
- // Packets and components
- private static final Class> CHAT_COMPONENT_CLASS;
- private static final Class> CHAT_FORMAT_ENUM;
- private static final Object EMPTY_MESSAGE;
- private static final Object RESET_FORMATTING;
private static final MethodHandle MESSAGE_FROM_STRING;
- private static final MethodHandle PLAYER_CONNECTION;
- private static final MethodHandle SEND_PACKET;
- private static final MethodHandle PLAYER_GET_HANDLE;
- // Scoreboard packets
- private static final FastReflection.PacketConstructor PACKET_SB_OBJ;
- private static final FastReflection.PacketConstructor PACKET_SB_DISPLAY_OBJ;
- private static final FastReflection.PacketConstructor PACKET_SB_SCORE;
- private static final FastReflection.PacketConstructor PACKET_SB_TEAM;
- private static final FastReflection.PacketConstructor PACKET_SB_SERIALIZABLE_TEAM;
- // Scoreboard enums
- private static final Class> ENUM_SB_HEALTH_DISPLAY;
- private static final Class> ENUM_SB_ACTION;
- private static final Object ENUM_SB_HEALTH_DISPLAY_INTEGER;
- private static final Object ENUM_SB_ACTION_CHANGE;
- private static final Object ENUM_SB_ACTION_REMOVE;
+ private static final Object EMPTY_MESSAGE;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
-
- if (FastReflection.isRepackaged()) {
- VERSION_TYPE = VersionType.V1_17;
- } else if (FastReflection.nmsOptionalClass(null, "ScoreboardServer$Action").isPresent()) {
- VERSION_TYPE = VersionType.V1_13;
- } else if (FastReflection.nmsOptionalClass(null, "IScoreboardCriteria$EnumScoreboardHealthDisplay").isPresent()) {
- VERSION_TYPE = VersionType.V1_8;
- } else {
- VERSION_TYPE = VersionType.V1_7;
- }
-
- String gameProtocolPackage = "network.protocol.game";
- Class> craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer");
Class> craftChatMessageClass = FastReflection.obcClass("util.CraftChatMessage");
- Class> entityPlayerClass = FastReflection.nmsClass("server.level", "EntityPlayer");
- Class> playerConnectionClass = FastReflection.nmsClass("server.network", "PlayerConnection");
- Class> packetClass = FastReflection.nmsClass("network.protocol", "Packet");
- Class> packetSbObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardObjective");
- Class> packetSbDisplayObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardDisplayObjective");
- Class> packetSbScoreClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardScore");
- Class> packetSbTeamClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam");
- Class> sbTeamClass = VersionType.V1_17.isHigherOrEqual()
- ? FastReflection.innerClass(packetSbTeamClass, innerClass -> !innerClass.isEnum()) : null;
- Field playerConnectionField = Arrays.stream(entityPlayerClass.getFields())
- .filter(field -> field.getType().isAssignableFrom(playerConnectionClass))
- .findFirst().orElseThrow(NoSuchFieldException::new);
- Method sendPacketMethod = Arrays.stream(playerConnectionClass.getMethods())
- .filter(m -> m.getParameterCount() == 1 && m.getParameterTypes()[0] == packetClass)
- .findFirst().orElseThrow(NoSuchMethodException::new);
-
MESSAGE_FROM_STRING = lookup.unreflect(craftChatMessageClass.getMethod("fromString", String.class));
- CHAT_COMPONENT_CLASS = FastReflection.nmsClass("network.chat", "IChatBaseComponent");
- CHAT_FORMAT_ENUM = FastReflection.nmsClass(null, "EnumChatFormat");
EMPTY_MESSAGE = Array.get(MESSAGE_FROM_STRING.invoke(""), 0);
- RESET_FORMATTING = FastReflection.enumValueOf(CHAT_FORMAT_ENUM, "RESET", 21);
- PLAYER_GET_HANDLE = lookup.findVirtual(craftPlayerClass, "getHandle", MethodType.methodType(entityPlayerClass));
- PLAYER_CONNECTION = lookup.unreflectGetter(playerConnectionField);
- SEND_PACKET = lookup.unreflect(sendPacketMethod);
- PACKET_SB_OBJ = FastReflection.findPacketConstructor(packetSbObjClass, lookup);
- PACKET_SB_DISPLAY_OBJ = FastReflection.findPacketConstructor(packetSbDisplayObjClass, lookup);
- PACKET_SB_SCORE = FastReflection.findPacketConstructor(packetSbScoreClass, lookup);
- PACKET_SB_TEAM = FastReflection.findPacketConstructor(packetSbTeamClass, lookup);
- PACKET_SB_SERIALIZABLE_TEAM = sbTeamClass == null ? null : FastReflection.findPacketConstructor(sbTeamClass, lookup);
-
- for (Class> clazz : Arrays.asList(packetSbObjClass, packetSbDisplayObjClass, packetSbScoreClass, packetSbTeamClass, sbTeamClass)) {
- 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()
- ? "ScoreboardServer$Action"
- : "PacketPlayOutScoreboardScore$EnumScoreboardAction";
- ENUM_SB_HEALTH_DISPLAY = FastReflection.nmsClass("world.scores.criteria", "IScoreboardCriteria$EnumScoreboardHealthDisplay");
- ENUM_SB_ACTION = FastReflection.nmsClass("server", enumSbActionClass);
- ENUM_SB_HEALTH_DISPLAY_INTEGER = FastReflection.enumValueOf(ENUM_SB_HEALTH_DISPLAY, "INTEGER", 0);
- ENUM_SB_ACTION_CHANGE = FastReflection.enumValueOf(ENUM_SB_ACTION, "CHANGE", 0);
- ENUM_SB_ACTION_REMOVE = FastReflection.enumValueOf(ENUM_SB_ACTION, "REMOVE", 1);
- } else {
- ENUM_SB_HEALTH_DISPLAY = null;
- ENUM_SB_ACTION = null;
- ENUM_SB_HEALTH_DISPLAY_INTEGER = null;
- ENUM_SB_ACTION_CHANGE = null;
- ENUM_SB_ACTION_REMOVE = null;
- }
} catch (Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
- private final Player player;
- private final String id;
-
- private final List lines = new ArrayList<>();
- private String title = ChatColor.RESET.toString();
-
- private boolean deleted = false;
-
/**
- * Creates a new FastBoard.
- *
- * @param player the owner of the scoreboard
+ * {@inheritDoc}
*/
public FastBoard(Player player) {
- this.player = Objects.requireNonNull(player, "player");
- this.id = "fb-" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
-
- try {
- sendObjectivePacket(ObjectiveMode.CREATE);
- sendDisplayObjectivePacket();
- } catch (Throwable t) {
- throw new RuntimeException("Unable to create scoreboard", t);
- }
+ super(player);
}
/**
- * Get the scoreboard title.
- *
- * @return the scoreboard title
- */
- public String getTitle() {
- return this.title;
- }
-
- /**
- * Update the scoreboard title.
- *
- * @param title the new scoreboard title
- * @throws IllegalArgumentException if the title is longer than 32 chars on 1.12 or lower
- * @throws IllegalStateException if {@link #delete()} was call before
+ * {@inheritDoc}
*/
+ @Override
public void updateTitle(String title) {
- if (this.title.equals(Objects.requireNonNull(title, "title"))) {
- return;
- }
+ Objects.requireNonNull(title, "title");
if (!VersionType.V1_13.isHigherOrEqual() && title.length() > 32) {
throw new IllegalArgumentException("Title is longer than 32 chars");
}
- this.title = title;
-
- try {
- sendObjectivePacket(ObjectiveMode.UPDATE);
- } catch (Throwable t) {
- throw new RuntimeException("Unable to update scoreboard title", t);
- }
+ super.updateTitle(title);
}
/**
- * Get the scoreboard lines.
- *
- * @return the scoreboard lines
- */
- public List getLines() {
- return new ArrayList<>(this.lines);
- }
-
- /**
- * Get the specified scoreboard line.
- *
- * @param line the line number
- * @return the line
- * @throws IndexOutOfBoundsException if the line is higher than {@code size}
- */
- public String getLine(int line) {
- checkLineNumber(line, true, false);
-
- return this.lines.get(line);
- }
-
- /**
- * Update a single scoreboard line.
- *
- * @param line the line number
- * @param text the new line text
- * @throws IndexOutOfBoundsException if the line is higher than {@link #size() size() + 1}
- */
- public synchronized void updateLine(int line, String text) {
- checkLineNumber(line, false, true);
-
- try {
- if (line < size()) {
- this.lines.set(line, text);
-
- sendTeamPacket(getScoreByLine(line), TeamMode.UPDATE);
- return;
- }
-
- List newLines = new ArrayList<>(this.lines);
-
- if (line > size()) {
- for (int i = size(); i < line; i++) {
- newLines.add("");
- }
- }
-
- newLines.add(text);
-
- updateLines(newLines);
- } catch (Throwable t) {
- throw new RuntimeException("Unable to update scoreboard lines", t);
- }
- }
-
- /**
- * Remove a scoreboard line.
- *
- * @param line the line number
- */
- public synchronized void removeLine(int line) {
- checkLineNumber(line, false, false);
-
- if (line >= size()) {
- return;
- }
-
- List newLines = new ArrayList<>(this.lines);
- newLines.remove(line);
- updateLines(newLines);
- }
-
- /**
- * Update all the scoreboard lines.
- *
- * @param lines the new lines
- * @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower
- * @throws IllegalStateException if {@link #delete()} was call before
+ * {@inheritDoc}
*/
+ @Override
public void updateLines(String... lines) {
- updateLines(Arrays.asList(lines));
- }
-
- /**
- * Update the lines of the scoreboard
- *
- * @param lines the new scoreboard lines
- * @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower
- * @throws IllegalStateException if {@link #delete()} was call before
- */
- public synchronized void updateLines(Collection lines) {
Objects.requireNonNull(lines, "lines");
- checkLineNumber(lines.size(), false, true);
if (!VersionType.V1_13.isHigherOrEqual()) {
int lineCount = 0;
@@ -304,97 +88,64 @@ public class FastBoard {
}
}
- List oldLines = new ArrayList<>(this.lines);
- this.lines.clear();
- this.lines.addAll(lines);
+ super.updateLines(lines);
+ }
- int linesSize = this.lines.size();
+ @Override
+ protected void sendLineChange(int score) throws Throwable {
+ int maxLength = hasLinesMaxLength() ? 16 : 1024;
+ String line = getLineByScore(score);
+ String prefix;
+ String suffix = "";
- try {
- if (oldLines.size() != linesSize) {
- List oldLinesCopy = new ArrayList<>(oldLines);
+ if (line == null || line.isEmpty()) {
+ prefix = COLOR_CODES[score] + ChatColor.RESET;
+ } else if (line.length() <= maxLength) {
+ prefix = line;
+ } else {
+ // Prevent splitting color codes
+ int index = line.charAt(maxLength - 1) == ChatColor.COLOR_CHAR
+ ? (maxLength - 1) : maxLength;
+ prefix = line.substring(0, index);
+ String suffixTmp = line.substring(index);
+ ChatColor chatColor = null;
- if (oldLines.size() > linesSize) {
- for (int i = oldLinesCopy.size(); i > linesSize; i--) {
- sendTeamPacket(i - 1, TeamMode.REMOVE);
- sendScorePacket(i - 1, ScoreboardAction.REMOVE);
-
- oldLines.remove(0);
- }
- } else {
- for (int i = oldLinesCopy.size(); i < linesSize; i++) {
- sendScorePacket(i, ScoreboardAction.CHANGE);
- sendTeamPacket(i, TeamMode.CREATE);
-
- oldLines.add(oldLines.size() - i, getLineByScore(i));
- }
- }
+ if (suffixTmp.length() >= 2 && suffixTmp.charAt(0) == ChatColor.COLOR_CHAR) {
+ chatColor = ChatColor.getByChar(suffixTmp.charAt(1));
}
- for (int i = 0; i < linesSize; i++) {
- if (!Objects.equals(getLineByScore(oldLines, i), getLineByScore(i))) {
- sendTeamPacket(i, TeamMode.UPDATE);
- }
- }
- } catch (Throwable t) {
- throw new RuntimeException("Unable to update scoreboard lines", t);
- }
- }
+ String color = ChatColor.getLastColors(prefix);
+ boolean addColor = chatColor == null || chatColor.isFormat();
- /**
- * Get the player who has the scoreboard.
- *
- * @return current player for this FastBoard
- */
- public Player getPlayer() {
- return this.player;
- }
-
- /**
- * Get the scoreboard id.
- *
- * @return the id
- */
- public String getId() {
- return this.id;
- }
-
- /**
- * Get if the scoreboard is deleted.
- *
- * @return true if the scoreboard is deleted
- */
- public boolean isDeleted() {
- return this.deleted;
- }
-
- /**
- * Get the scoreboard size (the number of lines).
- *
- * @return the size
- */
- public int size() {
- return this.lines.size();
- }
-
- /**
- * Delete this FastBoard, and will remove the scoreboard for the associated player if he is online.
- * After this, all uses of {@link #updateLines} and {@link #updateTitle} will throws an {@link IllegalStateException}
- *
- * @throws IllegalStateException if this was already call before
- */
- public void delete() {
- try {
- for (int i = 0; i < this.lines.size(); i++) {
- sendTeamPacket(i, TeamMode.REMOVE);
- }
-
- sendObjectivePacket(ObjectiveMode.REMOVE);
- } catch (Throwable t) {
- throw new RuntimeException("Unable to delete scoreboard", t);
+ suffix = (addColor ? (color.isEmpty() ? ChatColor.RESET.toString() : color) : "") + suffixTmp;
}
- this.deleted = true;
+ if (prefix.length() > maxLength || suffix.length() > maxLength) {
+ // Something went wrong, just cut to prevent client crash/kick
+ prefix = prefix.substring(0, maxLength);
+ suffix = suffix.substring(0, maxLength);
+ }
+
+ sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix);
+ }
+
+ @Override
+ protected Object toMinecraftComponent(String line) throws Throwable {
+ if (line == null || line.isEmpty()) {
+ return EMPTY_MESSAGE;
+ }
+
+ return Array.get(MESSAGE_FROM_STRING.invoke(line), 0);
+ }
+
+ @Override
+ protected String serializeLine(String value) {
+ return value;
+ }
+
+ @Override
+ protected String emptyLine() {
+ return "";
}
/**
@@ -407,205 +158,4 @@ public class FastBoard {
protected boolean hasLinesMaxLength() {
return !VersionType.V1_13.isHigherOrEqual();
}
-
- private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {
- if (line < 0) {
- throw new IllegalArgumentException("Line number must be positive");
- }
-
- if (checkInRange && line >= this.lines.size()) {
- throw new IllegalArgumentException("Line number must be under " + this.lines.size());
- }
-
- if (checkMax && line >= COLOR_CODES.length - 1) {
- throw new IllegalArgumentException("Line number is too high: " + line);
- }
- }
-
- private int getScoreByLine(int line) {
- return this.lines.size() - line - 1;
- }
-
- private String getLineByScore(int score) {
- return getLineByScore(this.lines, score);
- }
-
- private String getLineByScore(List lines, int score) {
- return lines.get(lines.size() - score - 1);
- }
-
- private void sendObjectivePacket(ObjectiveMode mode) throws Throwable {
- Object packet = PACKET_SB_OBJ.invoke();
-
- setField(packet, String.class, this.id);
- setField(packet, int.class, mode.ordinal());
-
- if (mode != ObjectiveMode.REMOVE) {
- setComponentField(packet, this.title, 1);
-
- if (VersionType.V1_8.isHigherOrEqual()) {
- setField(packet, ENUM_SB_HEALTH_DISPLAY, ENUM_SB_HEALTH_DISPLAY_INTEGER);
- }
- } else if (VERSION_TYPE == VersionType.V1_7) {
- setField(packet, String.class, "", 1);
- }
-
- sendPacket(packet);
- }
-
- private void sendDisplayObjectivePacket() throws Throwable {
- Object packet = PACKET_SB_DISPLAY_OBJ.invoke();
-
- setField(packet, int.class, 1); // Position (1: sidebar)
- setField(packet, String.class, this.id); // Score Name
-
- sendPacket(packet);
- }
-
- private void sendScorePacket(int score, ScoreboardAction action) throws Throwable {
- Object packet = PACKET_SB_SCORE.invoke();
-
- setField(packet, String.class, COLOR_CODES[score], 0); // Player Name
-
- if (VersionType.V1_8.isHigherOrEqual()) {
- setField(packet, ENUM_SB_ACTION, action == ScoreboardAction.REMOVE ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE);
- } else {
- setField(packet, int.class, action.ordinal(), 1); // Action
- }
-
- if (action == ScoreboardAction.CHANGE) {
- setField(packet, String.class, this.id, 1); // Objective Name
- setField(packet, int.class, score); // Score
- }
-
- sendPacket(packet);
- }
-
- private void sendTeamPacket(int score, TeamMode mode) throws Throwable {
- if (mode == TeamMode.ADD_PLAYERS || mode == TeamMode.REMOVE_PLAYERS) {
- throw new UnsupportedOperationException();
- }
-
- int maxLength = hasLinesMaxLength() ? 16 : 1024;
- Object packet = PACKET_SB_TEAM.invoke();
-
- setField(packet, String.class, this.id + ':' + score); // Team name
- setField(packet, int.class, mode.ordinal(), VERSION_TYPE == VersionType.V1_8 ? 1 : 0); // Update mode
-
- if (mode == TeamMode.CREATE || mode == TeamMode.UPDATE) {
- String line = getLineByScore(score);
- String prefix;
- String suffix = null;
-
- if (line == null || line.isEmpty()) {
- prefix = COLOR_CODES[score] + ChatColor.RESET;
- } else if (line.length() <= maxLength) {
- prefix = line;
- } else {
- // Prevent splitting color codes
- int index = line.charAt(maxLength - 1) == ChatColor.COLOR_CHAR ? (maxLength - 1) : maxLength;
- prefix = line.substring(0, index);
- String suffixTmp = line.substring(index);
- ChatColor chatColor = null;
-
- if (suffixTmp.length() >= 2 && suffixTmp.charAt(0) == ChatColor.COLOR_CHAR) {
- chatColor = ChatColor.getByChar(suffixTmp.charAt(1));
- }
-
- String color = ChatColor.getLastColors(prefix);
- boolean addColor = chatColor == null || chatColor.isFormat();
-
- suffix = (addColor ? (color.isEmpty() ? ChatColor.RESET.toString() : color) : "") + suffixTmp;
- }
-
- if (prefix.length() > maxLength || (suffix != null && suffix.length() > maxLength)) {
- // Something went wrong, just cut to prevent client crash/kick
- prefix = prefix.substring(0, maxLength);
- suffix = (suffix != null) ? suffix.substring(0, maxLength) : null;
- }
-
- if (VersionType.V1_17.isHigherOrEqual()) {
- Object team = PACKET_SB_SERIALIZABLE_TEAM.invoke();
- // Since the packet is initialized with null values, we need to change more things.
- setComponentField(team, "", 0); // Display name
- setField(team, CHAT_FORMAT_ENUM, RESET_FORMATTING); // Color
- setComponentField(team, prefix, 1); // Prefix
- setComponentField(team, suffix == null ? "" : suffix, 2); // Suffix
- setField(team, String.class, "always", 0); // Visibility
- setField(team, String.class, "always", 1); // Collisions
- setField(packet, Optional.class, Optional.of(team));
- } else {
- setComponentField(packet, prefix, 2); // Prefix
- setComponentField(packet, suffix == null ? "" : suffix, 3); // Suffix
- setField(packet, String.class, "always", 4); // Visibility for 1.8+
- setField(packet, String.class, "always", 5); // Collisions for 1.9+
- }
-
- if (mode == TeamMode.CREATE) {
- setField(packet, Collection.class, Collections.singletonList(COLOR_CODES[score])); // Players in the team
- }
- }
-
- sendPacket(packet);
- }
-
- private void sendPacket(Object packet) throws Throwable {
- if (this.deleted) {
- throw new IllegalStateException("This FastBoard is deleted");
- }
-
- if (this.player.isOnline()) {
- Object entityPlayer = PLAYER_GET_HANDLE.invoke(this.player);
- Object playerConnection = PLAYER_CONNECTION.invoke(entityPlayer);
- SEND_PACKET.invoke(playerConnection, packet);
- }
- }
-
- private void setField(Object object, Class> fieldType, Object value) throws ReflectiveOperationException {
- setField(object, fieldType, value, 0);
- }
-
- private void setField(Object packet, Class> fieldType, Object value, int count) throws ReflectiveOperationException {
- int i = 0;
- for (Field field : PACKETS.get(packet.getClass())) {
- if (field.getType() == fieldType && count == i++) {
- field.set(packet, value);
- }
- }
- }
-
- private void setComponentField(Object packet, String value, int count) throws Throwable {
- if (!VersionType.V1_13.isHigherOrEqual()) {
- setField(packet, String.class, value, count);
- return;
- }
-
- int i = 0;
- for (Field field : PACKETS.get(packet.getClass())) {
- if ((field.getType() == String.class || field.getType() == CHAT_COMPONENT_CLASS) && count == i++) {
- field.set(packet, value.isEmpty() ? EMPTY_MESSAGE : Array.get(MESSAGE_FROM_STRING.invoke(value), 0));
- }
- }
- }
-
- enum ObjectiveMode {
- CREATE, REMOVE, UPDATE
- }
-
- enum TeamMode {
- CREATE, REMOVE, UPDATE, ADD_PLAYERS, REMOVE_PLAYERS
- }
-
- enum ScoreboardAction {
- CHANGE, REMOVE
- }
-
- enum VersionType {
- V1_7, V1_8, V1_13, V1_17;
-
- public boolean isHigherOrEqual() {
- return VERSION_TYPE.ordinal() >= ordinal();
- }
- }
-}
-
+}
\ No newline at end of file
diff --git a/src/mc/ajneb97/lib/fastboard/FastBoardBase.java b/src/mc/ajneb97/lib/fastboard/FastBoardBase.java
new file mode 100644
index 0000000..5b74fee
--- /dev/null
+++ b/src/mc/ajneb97/lib/fastboard/FastBoardBase.java
@@ -0,0 +1,641 @@
+
+/*
+ * This file is part of FastBoard, licensed under the MIT License.
+ *
+ * Copyright (c) 2019-2023 MrMicky
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package mc.ajneb97.lib.fastboard;
+
+
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Stream;
+
+/**
+ * Lightweight packet-based scoreboard API for Bukkit plugins.
+ * It can be safely used asynchronously as everything is at packet level.
+ *
+ * The project is on GitHub.
+ *
+ * @author MrMicky
+ * @version 2.0.2
+ */
+public abstract class FastBoardBase {
+
+ private static final Map, Field[]> PACKETS = new HashMap<>(8);
+ protected static final String[] COLOR_CODES = Arrays.stream(ChatColor.values())
+ .map(Object::toString)
+ .toArray(String[]::new);
+ private static final VersionType VERSION_TYPE;
+ // Packets and components
+ private static final Class> CHAT_COMPONENT_CLASS;
+ private static final Class> CHAT_FORMAT_ENUM;
+ private static final Object RESET_FORMATTING;
+ private static final MethodHandle PLAYER_CONNECTION;
+ private static final MethodHandle SEND_PACKET;
+ private static final MethodHandle PLAYER_GET_HANDLE;
+ // Scoreboard packets
+ private static final FastReflection.PacketConstructor PACKET_SB_OBJ;
+ private static final FastReflection.PacketConstructor PACKET_SB_DISPLAY_OBJ;
+ private static final FastReflection.PacketConstructor PACKET_SB_TEAM;
+ private static final FastReflection.PacketConstructor PACKET_SB_SERIALIZABLE_TEAM;
+ private static final MethodHandle PACKET_SB_SET_SCORE;
+ private static final MethodHandle PACKET_SB_RESET_SCORE;
+ // Scoreboard enums
+ private static final Class> DISPLAY_SLOT_TYPE;
+ private static final Class> ENUM_SB_HEALTH_DISPLAY;
+ private static final Class> ENUM_SB_ACTION;
+ private static final Object BLANK_NUMBER_FORMAT;
+ private static final Object SIDEBAR_DISPLAY_SLOT;
+ private static final Object ENUM_SB_HEALTH_DISPLAY_INTEGER;
+ private static final Object ENUM_SB_ACTION_CHANGE;
+ private static final Object ENUM_SB_ACTION_REMOVE;
+
+ static {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+ if (FastReflection.isRepackaged()) {
+ VERSION_TYPE = VersionType.V1_17;
+ } else if (FastReflection.nmsOptionalClass(null, "ScoreboardServer$Action").isPresent()) {
+ VERSION_TYPE = VersionType.V1_13;
+ } else if (FastReflection.nmsOptionalClass(null, "IScoreboardCriteria$EnumScoreboardHealthDisplay").isPresent()) {
+ VERSION_TYPE = VersionType.V1_8;
+ } else {
+ VERSION_TYPE = VersionType.V1_7;
+ }
+
+ String gameProtocolPackage = "network.protocol.game";
+ Class> craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer");
+ Class> entityPlayerClass = FastReflection.nmsClass("server.level", "EntityPlayer");
+ Class> playerConnectionClass = FastReflection.nmsClass("server.network", "PlayerConnection");
+ Class> packetClass = FastReflection.nmsClass("network.protocol", "Packet");
+ Class> packetSbObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardObjective");
+ Class> packetSbDisplayObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardDisplayObjective");
+ Class> packetSbScoreClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardScore");
+ Class> packetSbTeamClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam");
+ Class> sbTeamClass = VersionType.V1_17.isHigherOrEqual()
+ ? FastReflection.innerClass(packetSbTeamClass, innerClass -> !innerClass.isEnum()) : null;
+ Field playerConnectionField = Arrays.stream(entityPlayerClass.getFields())
+ .filter(field -> field.getType().isAssignableFrom(playerConnectionClass))
+ .findFirst().orElseThrow(NoSuchFieldException::new);
+ Method sendPacketMethod = Stream.concat(
+ Arrays.stream(playerConnectionClass.getSuperclass().getMethods()),
+ Arrays.stream(playerConnectionClass.getMethods())
+ )
+ .filter(m -> m.getParameterCount() == 1 && m.getParameterTypes()[0] == packetClass)
+ .findFirst().orElseThrow(NoSuchMethodException::new);
+ Optional> displaySlotEnum = FastReflection.nmsOptionalClass("world.scores", "DisplaySlot");
+ CHAT_COMPONENT_CLASS = FastReflection.nmsClass("network.chat", "IChatBaseComponent");
+ CHAT_FORMAT_ENUM = FastReflection.nmsClass(null, "EnumChatFormat");
+ DISPLAY_SLOT_TYPE = displaySlotEnum.orElse(int.class);
+ RESET_FORMATTING = FastReflection.enumValueOf(CHAT_FORMAT_ENUM, "RESET", 21);
+ SIDEBAR_DISPLAY_SLOT = displaySlotEnum.isPresent() ? FastReflection.enumValueOf(DISPLAY_SLOT_TYPE, "SIDEBAR", 1) : 1;
+ PLAYER_GET_HANDLE = lookup.findVirtual(craftPlayerClass, "getHandle", MethodType.methodType(entityPlayerClass));
+ PLAYER_CONNECTION = lookup.unreflectGetter(playerConnectionField);
+ SEND_PACKET = lookup.unreflect(sendPacketMethod);
+ PACKET_SB_OBJ = FastReflection.findPacketConstructor(packetSbObjClass, lookup);
+ PACKET_SB_DISPLAY_OBJ = FastReflection.findPacketConstructor(packetSbDisplayObjClass, lookup);
+
+ Optional> numberFormat = FastReflection.nmsOptionalClass("network.chat.numbers", "NumberFormat");
+ MethodHandle packetSbSetScore;
+ MethodHandle packetSbResetScore = null;
+ Object blankNumberFormat = null;
+
+ if (numberFormat.isPresent()) { // 1.20.3
+ Class> blankFormatClass = FastReflection.nmsClass("network.chat.numbers", "BlankFormat");
+ Class> resetScoreClass = FastReflection.nmsClass(gameProtocolPackage, "ClientboundResetScorePacket");
+ MethodType setScoreType = MethodType.methodType(void.class, String.class, String.class, int.class, CHAT_COMPONENT_CLASS, numberFormat.get());
+ MethodType removeScoreType = MethodType.methodType(void.class, String.class, String.class);
+ Optional blankField = Arrays.stream(blankFormatClass.getFields()).filter(f -> f.getType() == blankFormatClass).findAny();
+ packetSbSetScore = lookup.findConstructor(packetSbScoreClass, setScoreType);
+ 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");
+ MethodType scoreType = MethodType.methodType(void.class, enumSbAction, String.class, String.class, int.class);
+ packetSbSetScore = lookup.findConstructor(packetSbScoreClass, scoreType);
+ } else {
+ packetSbSetScore = lookup.findConstructor(packetSbScoreClass, MethodType.methodType(void.class));
+ }
+
+ PACKET_SB_SET_SCORE = packetSbSetScore;
+ PACKET_SB_RESET_SCORE = packetSbResetScore;
+ PACKET_SB_TEAM = FastReflection.findPacketConstructor(packetSbTeamClass, lookup);
+ PACKET_SB_SERIALIZABLE_TEAM = sbTeamClass == null ? null : FastReflection.findPacketConstructor(sbTeamClass, lookup);
+ BLANK_NUMBER_FORMAT = blankNumberFormat;
+
+ for (Class> clazz : Arrays.asList(packetSbObjClass, packetSbDisplayObjClass, packetSbScoreClass, packetSbTeamClass, sbTeamClass)) {
+ 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()
+ ? "ScoreboardServer$Action"
+ : "PacketPlayOutScoreboardScore$EnumScoreboardAction";
+ ENUM_SB_HEALTH_DISPLAY = FastReflection.nmsClass("world.scores.criteria", "IScoreboardCriteria$EnumScoreboardHealthDisplay");
+ ENUM_SB_ACTION = FastReflection.nmsClass("server", enumSbActionClass);
+ ENUM_SB_HEALTH_DISPLAY_INTEGER = FastReflection.enumValueOf(ENUM_SB_HEALTH_DISPLAY, "INTEGER", 0);
+ ENUM_SB_ACTION_CHANGE = FastReflection.enumValueOf(ENUM_SB_ACTION, "CHANGE", 0);
+ ENUM_SB_ACTION_REMOVE = FastReflection.enumValueOf(ENUM_SB_ACTION, "REMOVE", 1);
+ } else {
+ ENUM_SB_HEALTH_DISPLAY = null;
+ ENUM_SB_ACTION = null;
+ ENUM_SB_HEALTH_DISPLAY_INTEGER = null;
+ ENUM_SB_ACTION_CHANGE = null;
+ ENUM_SB_ACTION_REMOVE = null;
+ }
+ } catch (Throwable t) {
+ throw new ExceptionInInitializerError(t);
+ }
+ }
+
+ private final Player player;
+ private final String id;
+
+ private final List lines = new ArrayList<>();
+ private T title = emptyLine();
+
+ private boolean deleted = false;
+
+ /**
+ * Creates a new FastBoard.
+ *
+ * @param player the owner of the scoreboard
+ */
+ protected FastBoardBase(Player player) {
+ this.player = Objects.requireNonNull(player, "player");
+ this.id = "fb-" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+
+ try {
+ sendObjectivePacket(ObjectiveMode.CREATE);
+ sendDisplayObjectivePacket();
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to create scoreboard", t);
+ }
+ }
+
+ /**
+ * Get the scoreboard title.
+ *
+ * @return the scoreboard title
+ */
+ public T getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Update the scoreboard title.
+ *
+ * @param title the new scoreboard title
+ * @throws IllegalArgumentException if the title is longer than 32 chars on 1.12 or lower
+ * @throws IllegalStateException if {@link #delete()} was call before
+ */
+ public void updateTitle(T title) {
+ if (this.title.equals(Objects.requireNonNull(title, "title"))) {
+ return;
+ }
+
+ this.title = title;
+
+ try {
+ sendObjectivePacket(ObjectiveMode.UPDATE);
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to update scoreboard title", t);
+ }
+ }
+
+ /**
+ * Get the scoreboard lines.
+ *
+ * @return the scoreboard lines
+ */
+ public List getLines() {
+ return new ArrayList<>(this.lines);
+ }
+
+ /**
+ * Get the specified scoreboard line.
+ *
+ * @param line the line number
+ * @return the line
+ * @throws IndexOutOfBoundsException if the line is higher than {@code size}
+ */
+ public T getLine(int line) {
+ checkLineNumber(line, true, false);
+
+ return this.lines.get(line);
+ }
+
+ /**
+ * Update a single scoreboard line.
+ *
+ * @param line the line number
+ * @param text the new line text
+ * @throws IndexOutOfBoundsException if the line is higher than {@link #size() size() + 1}
+ */
+ public synchronized void updateLine(int line, T text) {
+ checkLineNumber(line, false, true);
+
+ try {
+ if (line < size()) {
+ this.lines.set(line, text);
+
+ sendLineChange(getScoreByLine(line));
+ return;
+ }
+
+ List newLines = new ArrayList<>(this.lines);
+
+ if (line > size()) {
+ for (int i = size(); i < line; i++) {
+ newLines.add(emptyLine());
+ }
+ }
+
+ newLines.add(text);
+
+ updateLines(newLines);
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to update scoreboard lines", t);
+ }
+ }
+
+ /**
+ * Remove a scoreboard line.
+ *
+ * @param line the line number
+ */
+ public synchronized void removeLine(int line) {
+ checkLineNumber(line, false, false);
+
+ if (line >= size()) {
+ return;
+ }
+
+ List newLines = new ArrayList<>(this.lines);
+ newLines.remove(line);
+ updateLines(newLines);
+ }
+
+ /**
+ * Update all the scoreboard lines.
+ *
+ * @param lines the new lines
+ * @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower
+ * @throws IllegalStateException if {@link #delete()} was call before
+ */
+ public void updateLines(T... lines) {
+ updateLines(Arrays.asList(lines));
+ }
+
+ /**
+ * Update the lines of the scoreboard
+ *
+ * @param lines the new scoreboard lines
+ * @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower
+ * @throws IllegalStateException if {@link #delete()} was call before
+ */
+ public synchronized void updateLines(Collection lines) {
+ Objects.requireNonNull(lines, "lines");
+ checkLineNumber(lines.size(), false, true);
+
+ List oldLines = new ArrayList<>(this.lines);
+ this.lines.clear();
+ this.lines.addAll(lines);
+
+ int linesSize = this.lines.size();
+
+ try {
+ if (oldLines.size() != linesSize) {
+ List oldLinesCopy = new ArrayList<>(oldLines);
+
+ if (oldLines.size() > linesSize) {
+ for (int i = oldLinesCopy.size(); i > linesSize; i--) {
+ sendTeamPacket(i - 1, TeamMode.REMOVE);
+ sendScorePacket(i - 1, ScoreboardAction.REMOVE);
+
+ oldLines.remove(0);
+ }
+ } else {
+ for (int i = oldLinesCopy.size(); i < linesSize; i++) {
+ sendScorePacket(i, ScoreboardAction.CHANGE);
+ sendTeamPacket(i, TeamMode.CREATE, null, null);
+ }
+ }
+ }
+
+ for (int i = 0; i < linesSize; i++) {
+ if (!Objects.equals(getLineByScore(oldLines, i), getLineByScore(i))) {
+ sendLineChange(i);
+ }
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to update scoreboard lines", t);
+ }
+ }
+
+ /**
+ * Get the player who has the scoreboard.
+ *
+ * @return current player for this FastBoard
+ */
+ public Player getPlayer() {
+ return this.player;
+ }
+
+ /**
+ * Get the scoreboard id.
+ *
+ * @return the id
+ */
+ public String getId() {
+ return this.id;
+ }
+
+ /**
+ * Get if the scoreboard is deleted.
+ *
+ * @return true if the scoreboard is deleted
+ */
+ public boolean isDeleted() {
+ return this.deleted;
+ }
+
+ /**
+ * Get the scoreboard size (the number of lines).
+ *
+ * @return the size
+ */
+ public int size() {
+ return this.lines.size();
+ }
+
+ /**
+ * Delete this FastBoard, and will remove the scoreboard for the associated player if he is online.
+ * After this, all uses of {@link #updateLines} and {@link #updateTitle} will throw an {@link IllegalStateException}
+ *
+ * @throws IllegalStateException if this was already call before
+ */
+ public void delete() {
+ try {
+ for (int i = 0; i < this.lines.size(); i++) {
+ sendTeamPacket(i, TeamMode.REMOVE);
+ }
+
+ sendObjectivePacket(ObjectiveMode.REMOVE);
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to delete scoreboard", t);
+ }
+
+ this.deleted = true;
+ }
+
+ protected abstract void sendLineChange(int score) throws Throwable;
+
+ protected abstract Object toMinecraftComponent(T value) throws Throwable;
+
+ protected abstract String serializeLine(T value);
+
+ protected abstract T emptyLine();
+
+ private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {
+ if (line < 0) {
+ throw new IllegalArgumentException("Line number must be positive");
+ }
+
+ if (checkInRange && line >= this.lines.size()) {
+ throw new IllegalArgumentException("Line number must be under " + this.lines.size());
+ }
+
+ if (checkMax && line >= COLOR_CODES.length - 1) {
+ throw new IllegalArgumentException("Line number is too high: " + line);
+ }
+ }
+
+ protected int getScoreByLine(int line) {
+ return this.lines.size() - line - 1;
+ }
+
+ protected T getLineByScore(int score) {
+ return getLineByScore(this.lines, score);
+ }
+
+ protected T getLineByScore(List lines, int score) {
+ return score < lines.size() ? lines.get(lines.size() - score - 1) : null;
+ }
+
+ protected void sendObjectivePacket(ObjectiveMode mode) throws Throwable {
+ Object packet = PACKET_SB_OBJ.invoke();
+
+ setField(packet, String.class, this.id);
+ setField(packet, int.class, mode.ordinal());
+
+ if (mode != ObjectiveMode.REMOVE) {
+ setComponentField(packet, this.title, 1);
+
+ if (VersionType.V1_8.isHigherOrEqual()) {
+ setField(packet, ENUM_SB_HEALTH_DISPLAY, ENUM_SB_HEALTH_DISPLAY_INTEGER);
+ }
+ } else if (VERSION_TYPE == VersionType.V1_7) {
+ setField(packet, String.class, "", 1);
+ }
+
+ sendPacket(packet);
+ }
+
+ protected void sendDisplayObjectivePacket() throws Throwable {
+ Object packet = PACKET_SB_DISPLAY_OBJ.invoke();
+
+ setField(packet, DISPLAY_SLOT_TYPE, SIDEBAR_DISPLAY_SLOT); // Position
+ setField(packet, String.class, this.id); // Score Name
+
+ sendPacket(packet);
+ }
+
+ protected void sendScorePacket(int score, ScoreboardAction action) throws Throwable {
+ if (VersionType.V1_17.isHigherOrEqual()) {
+ sendModernScorePacket(score, action);
+ return;
+ }
+
+ Object packet = PACKET_SB_SET_SCORE.invoke();
+
+ setField(packet, String.class, COLOR_CODES[score], 0); // Player Name
+
+ if (VersionType.V1_8.isHigherOrEqual()) {
+ Object enumAction = action == ScoreboardAction.REMOVE
+ ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE;
+ setField(packet, ENUM_SB_ACTION, enumAction);
+ } else {
+ setField(packet, int.class, action.ordinal(), 1); // Action
+ }
+
+ if (action == ScoreboardAction.CHANGE) {
+ setField(packet, String.class, this.id, 1); // Objective Name
+ setField(packet, int.class, score); // Score
+ }
+
+ sendPacket(packet);
+ }
+
+ private 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;
+
+ if (PACKET_SB_RESET_SCORE == null) { // Pre 1.20.3
+ sendPacket(PACKET_SB_SET_SCORE.invoke(enumAction, this.id, objName, score));
+ return;
+ }
+
+ if (action == ScoreboardAction.REMOVE) {
+ sendPacket(PACKET_SB_RESET_SCORE.invoke(objName, this.id));
+ return;
+ }
+
+ sendPacket(PACKET_SB_SET_SCORE.invoke(objName, this.id, score, null, BLANK_NUMBER_FORMAT));
+ }
+
+ protected void sendTeamPacket(int score, TeamMode mode) throws Throwable {
+ sendTeamPacket(score, mode, null, null);
+ }
+
+ protected void sendTeamPacket(int score, TeamMode mode, T prefix, T suffix)
+ throws Throwable {
+ if (mode == TeamMode.ADD_PLAYERS || mode == TeamMode.REMOVE_PLAYERS) {
+ throw new UnsupportedOperationException();
+ }
+
+ Object packet = PACKET_SB_TEAM.invoke();
+
+ setField(packet, String.class, this.id + ':' + score); // Team name
+ setField(packet, int.class, mode.ordinal(), VERSION_TYPE == VersionType.V1_8 ? 1 : 0); // Update mode
+
+ if (mode == TeamMode.REMOVE) {
+ sendPacket(packet);
+ return;
+ }
+
+ if (VersionType.V1_17.isHigherOrEqual()) {
+ Object team = PACKET_SB_SERIALIZABLE_TEAM.invoke();
+ // Since the packet is initialized with null values, we need to change more things.
+ setComponentField(team, null, 0); // Display name
+ setField(team, CHAT_FORMAT_ENUM, RESET_FORMATTING); // Color
+ setComponentField(team, prefix, 1); // Prefix
+ setComponentField(team, suffix, 2); // Suffix
+ setField(team, String.class, "always", 0); // Visibility
+ setField(team, String.class, "always", 1); // Collisions
+ setField(packet, Optional.class, Optional.of(team));
+ } else {
+ setComponentField(packet, prefix, 2); // Prefix
+ setComponentField(packet, suffix, 3); // Suffix
+ setField(packet, String.class, "always", 4); // Visibility for 1.8+
+ setField(packet, String.class, "always", 5); // Collisions for 1.9+
+ }
+
+ if (mode == TeamMode.CREATE) {
+ setField(packet, Collection.class, Collections.singletonList(COLOR_CODES[score])); // Players in the team
+ }
+
+ sendPacket(packet);
+ }
+
+ private void sendPacket(Object packet) throws Throwable {
+ if (this.deleted) {
+ throw new IllegalStateException("This FastBoard is deleted");
+ }
+
+ if (this.player.isOnline()) {
+ Object entityPlayer = PLAYER_GET_HANDLE.invoke(this.player);
+ Object playerConnection = PLAYER_CONNECTION.invoke(entityPlayer);
+ SEND_PACKET.invoke(playerConnection, packet);
+ }
+ }
+
+ private void setField(Object object, Class> fieldType, Object value)
+ throws ReflectiveOperationException {
+ setField(object, fieldType, value, 0);
+ }
+
+ private void setField(Object packet, Class> fieldType, Object value, int count)
+ throws ReflectiveOperationException {
+ int i = 0;
+ for (Field field : PACKETS.get(packet.getClass())) {
+ if (field.getType() == fieldType && count == i++) {
+ field.set(packet, value);
+ }
+ }
+ }
+
+ private void setComponentField(Object packet, T value, int count) throws Throwable {
+ if (!VersionType.V1_13.isHigherOrEqual()) {
+ String line = value != null ? serializeLine(value) : "";
+ setField(packet, String.class, line, count);
+ return;
+ }
+
+ int i = 0;
+ for (Field field : PACKETS.get(packet.getClass())) {
+ if ((field.getType() == String.class || field.getType() == CHAT_COMPONENT_CLASS) && count == i++) {
+ field.set(packet, toMinecraftComponent(value));
+ }
+ }
+ }
+
+ public enum ObjectiveMode {
+ CREATE, REMOVE, UPDATE
+ }
+
+ public enum TeamMode {
+ CREATE, REMOVE, UPDATE, ADD_PLAYERS, REMOVE_PLAYERS
+ }
+
+ public enum ScoreboardAction {
+ CHANGE, REMOVE
+ }
+
+ enum VersionType {
+ V1_7, V1_8, V1_13, V1_17;
+
+ public boolean isHigherOrEqual() {
+ return VERSION_TYPE.ordinal() >= ordinal();
+ }
+ }
+}
diff --git a/src/mc/ajneb97/lib/fastboard/FastReflection.java b/src/mc/ajneb97/lib/fastboard/FastReflection.java
index c784a97..e4737c8 100644
--- a/src/mc/ajneb97/lib/fastboard/FastReflection.java
+++ b/src/mc/ajneb97/lib/fastboard/FastReflection.java
@@ -1,3 +1,28 @@
+
+
+/*
+ * This file is part of FastBoard, licensed under the MIT License.
+ *
+ * Copyright (c) 2019-2023 MrMicky
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
package mc.ajneb97.lib.fastboard;
import org.bukkit.Bukkit;
@@ -6,7 +31,6 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Predicate;
@@ -116,7 +140,8 @@ public final class FastReflection {
}
}
- Method allocateMethod = theUnsafe.getClass().getMethod("allocateInstance", Class.class);
+ MethodType allocateMethodType = MethodType.methodType(Object.class, Class.class);
+ MethodHandle allocateMethod = lookup.findVirtual(theUnsafe.getClass(), "allocateInstance", allocateMethodType);
return () -> allocateMethod.invoke(theUnsafe, packetClass);
}
diff --git a/src/mc/ajneb97/lib/titleapi/TitleAPI.java b/src/mc/ajneb97/lib/titleapi/TitleAPI.java
index a2a009b..ef4ea29 100644
--- a/src/mc/ajneb97/lib/titleapi/TitleAPI.java
+++ b/src/mc/ajneb97/lib/titleapi/TitleAPI.java
@@ -50,7 +50,7 @@ public class TitleAPI implements Listener {
TitleSendEvent titleSendEvent = new TitleSendEvent(player, title, subtitle);
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
if(title.isEmpty()) {
title = " ";
}
diff --git a/src/mc/ajneb97/managers/InventarioCoronacion.java b/src/mc/ajneb97/managers/InventarioCoronacion.java
index 36c98c4..c1eab18 100644
--- a/src/mc/ajneb97/managers/InventarioCoronacion.java
+++ b/src/mc/ajneb97/managers/InventarioCoronacion.java
@@ -39,7 +39,7 @@ public class InventarioCoronacion implements Listener {
//Torre
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
@@ -53,7 +53,7 @@ public class InventarioCoronacion implements Listener {
//Caballo
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
@@ -67,7 +67,7 @@ public class InventarioCoronacion implements Listener {
//Alfil
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
@@ -86,7 +86,7 @@ public class InventarioCoronacion implements Listener {
//Reina
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15")
|| Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18")
- || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
diff --git a/src/mc/ajneb97/otros/Utilidades.java b/src/mc/ajneb97/otros/Utilidades.java
index 88e866f..68b3dde 100644
--- a/src/mc/ajneb97/otros/Utilidades.java
+++ b/src/mc/ajneb97/otros/Utilidades.java
@@ -27,6 +27,7 @@ import mc.ajneb97.versiones.V1_16_R3;
import mc.ajneb97.versiones.V1_17;
import mc.ajneb97.versiones.V1_18;
import mc.ajneb97.versiones.V1_19;
+import mc.ajneb97.versiones.V1_20;
import mc.ajneb97.versiones.V1_8_R1;
import mc.ajneb97.versiones.V1_8_R2;
import mc.ajneb97.versiones.V1_8_R3;
@@ -71,7 +72,7 @@ public class Utilidades {
}
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES,ItemFlag.HIDE_ENCHANTS,ItemFlag.HIDE_UNBREAKABLE,ItemFlag.HIDE_POTION_EFFECTS);
if(Bukkit.getVersion().contains("1.15") || Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17")
- || Bukkit.getVersion().contains("1.18") || Bukkit.getVersion().contains("1.19")) {
+ || Bukkit.getVersion().contains("1.18") || Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.20")) {
meta.setUnbreakable(true);
}else {
meta.spigot().setUnbreakable(true); //SOLO FUNCIONA CON SPIGOT
@@ -83,6 +84,10 @@ public class Utilidades {
public static void setSkullBlock(Location l, String id, String textura, int rot) {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
+ if(packageName.contains("1_20")){
+ V1_20 u = new V1_20();
+ u.setSkullBlock(l,id,textura,rot);
+ }else
if(packageName.contains("1_19")){
V1_19 u = new V1_19();
u.setSkullBlock(l,id,textura,rot);
@@ -159,6 +164,10 @@ public class Utilidades {
public static void generarParticula(String particle, Location loc, float xOffset, float yOffset, float zOffset, float speed, int count, Player player) {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
+ if(packageName.contains("1_20")){
+ V1_20 u = new V1_20();
+ u.generarParticula(particle, loc, xOffset, yOffset, zOffset, speed, count, player);
+ }
if(packageName.contains("1_19")){
V1_19 u = new V1_19();
u.generarParticula(particle, loc, xOffset, yOffset, zOffset, speed, count, player);
@@ -264,6 +273,11 @@ public class Utilidades {
public static ItemStack getCabeza(ItemStack item, String id,String textura){
String packageName = Bukkit.getServer().getClass().getPackage().getName();
+ if(packageName.contains("1_20")){
+ V1_20 u = new V1_20();
+ ItemStack stack = u.getCabeza(item,id,textura);
+ return stack;
+ }
if(packageName.contains("1_19")){
V1_19 u = new V1_19();
ItemStack stack = u.getCabeza(item,id,textura);
diff --git a/src/mc/ajneb97/versiones/V1_20.java b/src/mc/ajneb97/versiones/V1_20.java
new file mode 100644
index 0000000..7644b16
--- /dev/null
+++ b/src/mc/ajneb97/versiones/V1_20.java
@@ -0,0 +1,95 @@
+package mc.ajneb97.versiones;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Color;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Particle;
+import org.bukkit.SkullType;
+import org.bukkit.block.BlockFace;
+import org.bukkit.block.Skull;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.SkullMeta;
+import org.bukkit.profile.PlayerProfile;
+import org.bukkit.profile.PlayerTextures;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+
+public class V1_20 {
+
+ public void generarParticula(String particle, Location loc, float xOffset, float yOffset, float zOffset, float speed, int count, Player player){
+ player.spawnParticle(Particle.valueOf(particle),loc,count,xOffset,yOffset,zOffset,speed);
+ }
+
+
+ @SuppressWarnings("deprecation")
+ public void setSkullBlock(Location locBloque,String id,String textura,int rot) {
+ locBloque.getBlock().setType(Material.valueOf("PLAYER_HEAD"));
+ Skull skullBlock = (Skull) locBloque.getBlock().getState();
+ //skullBlock.setSkullType(SkullType.PLAYER);
+ if(rot == 8) {
+ skullBlock.setRotation(BlockFace.NORTH);
+ }
+
+ UUID uuid = UUID.randomUUID();
+ try {
+ PlayerProfile profile = (PlayerProfile) Bukkit.class.getMethod("createPlayerProfile", UUID.class).invoke(null,uuid);
+ PlayerTextures textures = profile.getTextures();
+ String decoded = new String(Base64.getDecoder().decode(textura));
+ String decodedFormatted = decoded.replaceAll("\\s", "");
+ int firstIndex = decodedFormatted.indexOf("\"SKIN\":{\"url\":")+15;
+ int lastIndex = decodedFormatted.indexOf("}",firstIndex+1);
+ URL url = new URL(decodedFormatted.substring(firstIndex,lastIndex-1));
+ textures.setSkin(url);
+ profile.setTextures(textures);
+ skullBlock.getClass().getMethod("setOwnerProfile", PlayerProfile.class).invoke(skullBlock, profile);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
+ | SecurityException | MalformedURLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ skullBlock.update();
+ }
+
+ public ItemStack getCabeza(ItemStack item, String id,String textura) {
+ if (textura.isEmpty()) return item;
+
+ SkullMeta skullMeta = (SkullMeta) item.getItemMeta();
+ UUID uuid = UUID.randomUUID();
+ try {
+ PlayerProfile profile = (PlayerProfile) Bukkit.class.getMethod("createPlayerProfile", UUID.class).invoke(null,uuid);
+ PlayerTextures textures = profile.getTextures();
+ String decoded = new String(Base64.getDecoder().decode(textura));
+ String decodedFormatted = decoded.replaceAll("\\s", "");
+ int firstIndex = decodedFormatted.indexOf("\"SKIN\":{\"url\":")+15;
+ int lastIndex = decodedFormatted.indexOf("}",firstIndex+1);
+ URL url = new URL(decodedFormatted.substring(firstIndex,lastIndex-1));
+ textures.setSkin(url);
+ profile.setTextures(textures);
+ Method method = skullMeta.getClass().getMethod("setOwnerProfile", PlayerProfile.class);
+ method.setAccessible(true);
+ method.invoke(skullMeta, profile);
+ item.setItemMeta(skullMeta);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
+ | SecurityException | MalformedURLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return item;
+ }
+}