This commit is contained in:
Ajneb97 2024-01-21 11:12:38 -03:00
parent 405eb3e246
commit 690ea97aa3
10 changed files with 872 additions and 547 deletions

View File

@ -1,5 +1,5 @@
main: mc.ajneb97.MineChess main: mc.ajneb97.MineChess
version: 1.14.1 version: 1.16.1
name: MineChess name: MineChess
api-version: 1.13 api-version: 1.13
softdepend: [Multiverse-Core,HolographicDisplays,PlaceholderAPI] softdepend: [Multiverse-Core,HolographicDisplays,PlaceholderAPI]

View File

@ -21,7 +21,7 @@ public class Tablero {
if(color.equals("b")) { if(color.equals("b")) {
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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"); m = Material.valueOf("WHITE_WOOL");
}else { }else {
m = Material.WOOL; m = Material.WOOL;
@ -39,7 +39,7 @@ public class Tablero {
}else { }else {
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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"); m = Material.valueOf("GRAY_WOOL");
l.getBlock().setType(m); l.getBlock().setType(m);
l.clone().add(1,0,0).getBlock().setType(m); l.clone().add(1,0,0).getBlock().setType(m);

View File

@ -17,7 +17,7 @@ public class ActionBarAPI
public static void sendActionBar(Player player, String message) public static void sendActionBar(Player player, String message)
{ {
if(Bukkit.getVersion().contains("1.16") || Bukkit.getVersion().contains("1.17") || Bukkit.getVersion().contains("1.18") 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); TextComponent text_component = new TextComponent(message);
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, text_component); player.spigot().sendMessage(ChatMessageType.ACTION_BAR, text_component);
return; return;

View File

@ -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; package mc.ajneb97.lib.fastboard;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@ -5,294 +28,55 @@ import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array; 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.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
/** /**
* Lightweight packet-based scoreboard API for Bukkit plugins. * {@inheritDoc}
* It can be safely used asynchronously as everything is at packet level.
* <p>
* The project is on <a href="https://github.com/MrMicky-FR/FastBoard">GitHub</a>.
*
* @author MrMicky
* @version 1.2.0-SNAPSHOT
*/ */
public class FastBoard { public class FastBoard extends FastBoardBase<String> {
private static final Map<Class<?>, 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 MESSAGE_FROM_STRING;
private static final MethodHandle PLAYER_CONNECTION; private static final Object EMPTY_MESSAGE;
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;
static { static {
try { try {
MethodHandles.Lookup lookup = MethodHandles.lookup(); 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<?> 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)); 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); 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) { } catch (Throwable t) {
throw new ExceptionInInitializerError(t); throw new ExceptionInInitializerError(t);
} }
} }
private final Player player;
private final String id;
private final List<String> lines = new ArrayList<>();
private String title = ChatColor.RESET.toString();
private boolean deleted = false;
/** /**
* Creates a new FastBoard. * {@inheritDoc}
*
* @param player the owner of the scoreboard
*/ */
public FastBoard(Player player) { public FastBoard(Player player) {
this.player = Objects.requireNonNull(player, "player"); super(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. * {@inheritDoc}
*
* @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
*/ */
@Override
public void updateTitle(String title) { public void updateTitle(String title) {
if (this.title.equals(Objects.requireNonNull(title, "title"))) { Objects.requireNonNull(title, "title");
return;
}
if (!VersionType.V1_13.isHigherOrEqual() && title.length() > 32) { if (!VersionType.V1_13.isHigherOrEqual() && title.length() > 32) {
throw new IllegalArgumentException("Title is longer than 32 chars"); throw new IllegalArgumentException("Title is longer than 32 chars");
} }
this.title = title; super.updateTitle(title);
try {
sendObjectivePacket(ObjectiveMode.UPDATE);
} catch (Throwable t) {
throw new RuntimeException("Unable to update scoreboard title", t);
}
} }
/** /**
* Get the scoreboard lines. * {@inheritDoc}
*
* @return the scoreboard lines
*/
public List<String> 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<String> 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<String> 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
*/ */
@Override
public void updateLines(String... lines) { 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<String> lines) {
Objects.requireNonNull(lines, "lines"); Objects.requireNonNull(lines, "lines");
checkLineNumber(lines.size(), false, true);
if (!VersionType.V1_13.isHigherOrEqual()) { if (!VersionType.V1_13.isHigherOrEqual()) {
int lineCount = 0; int lineCount = 0;
@ -304,198 +88,15 @@ public class FastBoard {
} }
} }
List<String> oldLines = new ArrayList<>(this.lines); super.updateLines(lines);
this.lines.clear();
this.lines.addAll(lines);
int linesSize = this.lines.size();
try {
if (oldLines.size() != linesSize) {
List<String> 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);
oldLines.add(oldLines.size() - i, getLineByScore(i));
}
}
}
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);
}
}
/**
* 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);
}
this.deleted = true;
}
/**
* Return if the player has a prefix/suffix characters limit.
* By default, it returns true only in 1.12 or lower.
* This method can be overridden to fix compatibility with some versions support plugin.
*
* @return max length
*/
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<String> 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();
} }
@Override
protected void sendLineChange(int score) throws Throwable {
int maxLength = hasLinesMaxLength() ? 16 : 1024; 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 line = getLineByScore(score);
String prefix; String prefix;
String suffix = null; String suffix = "";
if (line == null || line.isEmpty()) { if (line == null || line.isEmpty()) {
prefix = COLOR_CODES[score] + ChatColor.RESET; prefix = COLOR_CODES[score] + ChatColor.RESET;
@ -503,7 +104,8 @@ public class FastBoard {
prefix = line; prefix = line;
} else { } else {
// Prevent splitting color codes // Prevent splitting color codes
int index = line.charAt(maxLength - 1) == ChatColor.COLOR_CHAR ? (maxLength - 1) : maxLength; int index = line.charAt(maxLength - 1) == ChatColor.COLOR_CHAR
? (maxLength - 1) : maxLength;
prefix = line.substring(0, index); prefix = line.substring(0, index);
String suffixTmp = line.substring(index); String suffixTmp = line.substring(index);
ChatColor chatColor = null; ChatColor chatColor = null;
@ -518,94 +120,42 @@ public class FastBoard {
suffix = (addColor ? (color.isEmpty() ? ChatColor.RESET.toString() : color) : "") + suffixTmp; suffix = (addColor ? (color.isEmpty() ? ChatColor.RESET.toString() : color) : "") + suffixTmp;
} }
if (prefix.length() > maxLength || (suffix != null && suffix.length() > maxLength)) { if (prefix.length() > maxLength || suffix.length() > maxLength) {
// Something went wrong, just cut to prevent client crash/kick // Something went wrong, just cut to prevent client crash/kick
prefix = prefix.substring(0, maxLength); prefix = prefix.substring(0, maxLength);
suffix = (suffix != null) ? suffix.substring(0, maxLength) : null; suffix = suffix.substring(0, maxLength);
} }
if (VersionType.V1_17.isHigherOrEqual()) { sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix);
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) { @Override
setField(packet, Collection.class, Collections.singletonList(COLOR_CODES[score])); // Players in the team protected Object toMinecraftComponent(String line) throws Throwable {
} if (line == null || line.isEmpty()) {
return EMPTY_MESSAGE;
} }
sendPacket(packet); return Array.get(MESSAGE_FROM_STRING.invoke(line), 0);
} }
private void sendPacket(Object packet) throws Throwable { @Override
if (this.deleted) { protected String serializeLine(String value) {
throw new IllegalStateException("This FastBoard is deleted"); return value;
} }
if (this.player.isOnline()) { @Override
Object entityPlayer = PLAYER_GET_HANDLE.invoke(this.player); protected String emptyLine() {
Object playerConnection = PLAYER_CONNECTION.invoke(entityPlayer); return "";
SEND_PACKET.invoke(playerConnection, packet);
}
} }
private void setField(Object object, Class<?> fieldType, Object value) throws ReflectiveOperationException { /**
setField(object, fieldType, value, 0); * Return if the player has a prefix/suffix characters limit.
} * By default, it returns true only in 1.12 or lower.
* This method can be overridden to fix compatibility with some versions support plugin.
private void setField(Object packet, Class<?> fieldType, Object value, int count) throws ReflectiveOperationException { *
int i = 0; * @return max length
for (Field field : PACKETS.get(packet.getClass())) { */
if (field.getType() == fieldType && count == i++) { protected boolean hasLinesMaxLength() {
field.set(packet, value); return !VersionType.V1_13.isHigherOrEqual();
}
}
}
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();
}
} }
} }

View File

@ -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.
* <p>
* The project is on <a href="https://github.com/MrMicky-FR/FastBoard">GitHub</a>.
*
* @author MrMicky
* @version 2.0.2
*/
public abstract class FastBoardBase<T> {
private static final Map<Class<?>, 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<Class<?>> 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<Class<?>> 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<Field> 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<T> 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<T> 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<T> 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<T> 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<T> lines) {
Objects.requireNonNull(lines, "lines");
checkLineNumber(lines.size(), false, true);
List<T> oldLines = new ArrayList<>(this.lines);
this.lines.clear();
this.lines.addAll(lines);
int linesSize = this.lines.size();
try {
if (oldLines.size() != linesSize) {
List<T> 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<T> 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();
}
}
}

View File

@ -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; package mc.ajneb97.lib.fastboard;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -6,7 +31,6 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; 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); return () -> allocateMethod.invoke(theUnsafe, packetClass);
} }

View File

@ -50,7 +50,7 @@ public class TitleAPI implements Listener {
TitleSendEvent titleSendEvent = new TitleSendEvent(player, title, subtitle); TitleSendEvent titleSendEvent = new TitleSendEvent(player, title, subtitle);
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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()) { if(title.isEmpty()) {
title = " "; title = " ";
} }

View File

@ -39,7 +39,7 @@ public class InventarioCoronacion implements Listener {
//Torre //Torre
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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); item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else { }else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3); item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
@ -53,7 +53,7 @@ public class InventarioCoronacion implements Listener {
//Caballo //Caballo
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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); item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else { }else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3); item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
@ -67,7 +67,7 @@ public class InventarioCoronacion implements Listener {
//Alfil //Alfil
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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); item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else { }else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3); item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);
@ -86,7 +86,7 @@ public class InventarioCoronacion implements Listener {
//Reina //Reina
if(Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14") || Bukkit.getVersion().contains("1.15") 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.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); item = new ItemStack(Material.valueOf("PLAYER_HEAD"),1);
}else { }else {
item = new ItemStack(Material.SKULL_ITEM,1,(short) 3); item = new ItemStack(Material.SKULL_ITEM,1,(short) 3);

View File

@ -27,6 +27,7 @@ import mc.ajneb97.versiones.V1_16_R3;
import mc.ajneb97.versiones.V1_17; import mc.ajneb97.versiones.V1_17;
import mc.ajneb97.versiones.V1_18; import mc.ajneb97.versiones.V1_18;
import mc.ajneb97.versiones.V1_19; 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_R1;
import mc.ajneb97.versiones.V1_8_R2; import mc.ajneb97.versiones.V1_8_R2;
import mc.ajneb97.versiones.V1_8_R3; 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); 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") 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); meta.setUnbreakable(true);
}else { }else {
meta.spigot().setUnbreakable(true); //SOLO FUNCIONA CON SPIGOT 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) { public static void setSkullBlock(Location l, String id, String textura, int rot) {
String packageName = Bukkit.getServer().getClass().getPackage().getName(); 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")){ if(packageName.contains("1_19")){
V1_19 u = new V1_19(); V1_19 u = new V1_19();
u.setSkullBlock(l,id,textura,rot); 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) { 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(); 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")){ if(packageName.contains("1_19")){
V1_19 u = new V1_19(); V1_19 u = new V1_19();
u.generarParticula(particle, loc, xOffset, yOffset, zOffset, speed, count, player); 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){ public static ItemStack getCabeza(ItemStack item, String id,String textura){
String packageName = Bukkit.getServer().getClass().getPackage().getName(); 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")){ if(packageName.contains("1_19")){
V1_19 u = new V1_19(); V1_19 u = new V1_19();
ItemStack stack = u.getCabeza(item,id,textura); ItemStack stack = u.getCabeza(item,id,textura);

View File

@ -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;
}
}