From 0266b25548b1cebb3478a317620316f05c8208f5 Mon Sep 17 00:00:00 2001 From: vytskalt Date: Wed, 28 Feb 2024 20:30:45 +0200 Subject: [PATCH] Add WrappedTeamParameters --- .../protocol/events/AbstractStructure.java | 10 + .../protocol/utility/MinecraftReflection.java | 9 + .../protocol/wrappers/EnumWrappers.java | 142 +++++++++++++++ .../wrappers/WrappedTeamParameters.java | 171 ++++++++++++++++++ .../wrappers/WrappedTeamParametersTest.java | 53 ++++++ 5 files changed, 385 insertions(+) create mode 100644 src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java create mode 100644 src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index 4a16d15c..cccbfa67 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -877,6 +877,16 @@ public abstract class AbstractStructure { EnumWrappers.getRenderTypeConverter()); } + /** + * Retrieve a read/write structure for the ChatFormatting enum. + * @return A modifier for ChatFormatting enum fields. + */ + public StructureModifier getChatFormattings() { + return structureModifier.withType( + EnumWrappers.getChatFormattingClass(), + EnumWrappers.getChatFormattingConverter()); + } + /** * Retrieve a read/write structure for the MinecraftKey class. diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 69a0f203..b808c17a 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1009,6 +1009,15 @@ public final class MinecraftReflection { return getMinecraftClass("world.level.block.entity.TileEntity", "world.level.block.entity.BlockEntity", "TileEntity"); } + /** + * Retrieve the NMS team parameters class. + * + * @return The team parameters class. + */ + public static Optional> getTeamParametersClass() { + return getOptionalNMS("network.protocol.game.PacketPlayOutScoreboardTeam$b"); + } + /** * Retrieve the Gson class used by Minecraft. * diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index 10d2f2ff..0606825a 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -10,6 +10,7 @@ import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; import org.bukkit.GameMode; import java.lang.reflect.Field; @@ -490,6 +491,133 @@ public abstract class EnumWrappers { HEARTS } + public enum ChatFormatting { + BLACK, + DARK_BLUE, + DARK_GREEN, + DARK_AQUA, + DARK_RED, + DARK_PURPLE, + GOLD, + GRAY, + DARK_GRAY, + BLUE, + GREEN, + AQUA, + RED, + LIGHT_PURPLE, + YELLOW, + WHITE, + OBFUSCATED, + BOLD, + STRIKETHROUGH, + UNDERLINE, + ITALIC, + RESET; + + public ChatColor toBukkit() { + switch (this){ + case BLACK: + return ChatColor.BLACK; + case DARK_BLUE: + return ChatColor.DARK_BLUE; + case DARK_GREEN: + return ChatColor.DARK_GREEN; + case DARK_AQUA: + return ChatColor.DARK_AQUA; + case DARK_RED: + return ChatColor.DARK_RED; + case DARK_PURPLE: + return ChatColor.DARK_PURPLE; + case GOLD: + return ChatColor.GOLD; + case GRAY: + return ChatColor.GRAY; + case DARK_GRAY: + return ChatColor.DARK_GRAY; + case BLUE: + return ChatColor.BLUE; + case GREEN: + return ChatColor.GREEN; + case AQUA: + return ChatColor.AQUA; + case RED: + return ChatColor.RED; + case LIGHT_PURPLE: + return ChatColor.LIGHT_PURPLE; + case YELLOW: + return ChatColor.YELLOW; + case WHITE: + return ChatColor.WHITE; + case OBFUSCATED: + return ChatColor.MAGIC; + case BOLD: + return ChatColor.BOLD; + case STRIKETHROUGH: + return ChatColor.STRIKETHROUGH; + case UNDERLINE: + return ChatColor.UNDERLINE; + case ITALIC: + return ChatColor.ITALIC; + case RESET: + return ChatColor.RESET; + default: + throw new IllegalStateException("Unimplemented Bukkit equivalent for " + name()); + } + } + + public static ChatFormatting fromBukkit(ChatColor color) { + switch (color){ + case BLACK: + return ChatFormatting.BLACK; + case DARK_BLUE: + return ChatFormatting.DARK_BLUE; + case DARK_GREEN: + return ChatFormatting.DARK_GREEN; + case DARK_AQUA: + return ChatFormatting.DARK_AQUA; + case DARK_RED: + return ChatFormatting.DARK_RED; + case DARK_PURPLE: + return ChatFormatting.DARK_PURPLE; + case GOLD: + return ChatFormatting.GOLD; + case GRAY: + return ChatFormatting.GRAY; + case DARK_GRAY: + return ChatFormatting.DARK_GRAY; + case BLUE: + return ChatFormatting.BLUE; + case GREEN: + return ChatFormatting.GREEN; + case AQUA: + return ChatFormatting.AQUA; + case RED: + return ChatFormatting.RED; + case LIGHT_PURPLE: + return ChatFormatting.LIGHT_PURPLE; + case YELLOW: + return ChatFormatting.YELLOW; + case WHITE: + return ChatFormatting.WHITE; + case MAGIC: + return ChatFormatting.OBFUSCATED; + case BOLD: + return ChatFormatting.BOLD; + case STRIKETHROUGH: + return ChatFormatting.STRIKETHROUGH; + case UNDERLINE: + return ChatFormatting.UNDERLINE; + case ITALIC: + return ChatFormatting.ITALIC; + case RESET: + return ChatFormatting.RESET; + default: + throw new IllegalStateException("Unknown ChatColor " + color); + } + } + } + private static Class PROTOCOL_CLASS = null; private static Class CLIENT_COMMAND_CLASS = null; private static Class CHAT_VISIBILITY_CLASS = null; @@ -513,6 +641,7 @@ public abstract class EnumWrappers { private static Class ENTITY_POSE_CLASS = null; private static Class DISPLAY_SLOT_CLASS = null; private static Class RENDER_TYPE_CLASS = null; + private static Class CHAT_FORMATTING_CLASS = null; private static boolean INITIALIZED = false; private static Map, EquivalentConverter> FROM_NATIVE = new HashMap<>(); @@ -598,6 +727,7 @@ public abstract class EnumWrappers { ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose"); DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot"); RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS("world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay", "IScoreboardCriteria$EnumScoreboardHealthDisplay"); + CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("EnumChatFormat"); associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter()); associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter()); @@ -619,6 +749,9 @@ public abstract class EnumWrappers { associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter()); associate(HAND_CLASS, Hand.class, getHandConverter()); associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter()); + associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter()); + associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter()); + associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter()); if (ENTITY_POSE_CLASS != null) { associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter()); @@ -780,6 +913,11 @@ public abstract class EnumWrappers { return RENDER_TYPE_CLASS; } + public static Class getChatFormattingClass() { + initialize(); + return CHAT_FORMATTING_CLASS; + } + // Get the converters public static EquivalentConverter getProtocolConverter() { return new EnumConverter<>(getProtocolClass(), Protocol.class); @@ -869,6 +1007,10 @@ public abstract class EnumWrappers { return new EnumConverter<>(getRenderTypeClass(), RenderType.class); } + public static EquivalentConverter getChatFormattingConverter() { + return new EnumConverter<>(getChatFormattingClass(), ChatFormatting.class); + } + /** * @since 1.13+ * @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found) diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java new file mode 100644 index 00000000..f2aa9f2f --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java @@ -0,0 +1,171 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.injector.StructureCache; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wrapper around the team parameters NMS class. + * + * @author vytskalt + * @since 1.17 + */ +public class WrappedTeamParameters extends AbstractWrapper { + private static Class getNmsClass() { + return MinecraftReflection.getTeamParametersClass() + .orElseThrow(() -> new IllegalStateException("Team parameters class doesn't exist on this server version")); + } + + /** + * @return Whether the team parameters class exists on the current server version + */ + public static boolean isSupported() { + return MinecraftReflection.getTeamParametersClass().isPresent(); + } + + public static Builder newBuilder() { + return newBuilder(null); + } + + public static Builder newBuilder(@Nullable WrappedTeamParameters template) { + return new Builder(template); + } + + private final StructureModifier modifier; + + public WrappedTeamParameters(Object handle) { + super(getNmsClass()); + setHandle(handle); + this.modifier = new StructureModifier<>(getNmsClass()).withTarget(handle); + } + + @NotNull + public WrappedChatComponent getDisplayName() { + return readComponent(0); + } + + @NotNull + public WrappedChatComponent getPrefix() { + return readComponent(1); + } + + @NotNull + public WrappedChatComponent getSuffix() { + return readComponent(2); + } + + @NotNull + public String getNametagVisibility() { + return modifier.withType(String.class).read(0); + } + + @NotNull + public String getCollisionRule() { + return modifier.withType(String.class).read(1); + } + + @NotNull + public EnumWrappers.ChatFormatting getColor() { + Object handle = modifier.withType(EnumWrappers.getChatFormattingClass()).read(0); + return EnumWrappers.getChatFormattingConverter().getSpecific(handle); + } + + public int getOptions() { + return (int) modifier.withType(int.class).read(0); + } + + private WrappedChatComponent readComponent(int index) { + Object handle = modifier.withType(MinecraftReflection.getIChatBaseComponentClass()).read(index); + return WrappedChatComponent.fromHandle(handle); + } + + private void writeComponent(int index, WrappedChatComponent component) { + modifier.withType(MinecraftReflection.getIChatBaseComponentClass()).write(index, component.getHandle()); + } + + public static class Builder { + private WrappedChatComponent displayName, prefix, suffix; + private String nametagVisibility, collisionRule; + private EnumWrappers.ChatFormatting color; + private int options; + + private Builder(@Nullable WrappedTeamParameters template) { + if (template != null) { + this.displayName = template.getDisplayName(); + this.prefix = template.getDisplayName(); + this.suffix = template.getDisplayName(); + this.nametagVisibility = template.getNametagVisibility(); + this.collisionRule = template.getCollisionRule(); + this.color = template.getColor(); + this.options = template.getOptions(); + } + } + + public Builder displayName(@NotNull WrappedChatComponent displayName) { + Preconditions.checkNotNull(displayName); + this.displayName = displayName; + return this; + } + + public Builder prefix(@NotNull WrappedChatComponent prefix) { + Preconditions.checkNotNull(prefix); + this.prefix = prefix; + return this; + } + + public Builder suffix(@NotNull WrappedChatComponent suffix) { + Preconditions.checkNotNull(suffix); + this.suffix = suffix; + return this; + } + + public Builder nametagVisibility(@NotNull String nametagVisibility) { + Preconditions.checkNotNull(nametagVisibility); + this.nametagVisibility = nametagVisibility; + return this; + } + + public Builder collisionRule(@NotNull String collisionRule) { + Preconditions.checkNotNull(collisionRule); + this.collisionRule = collisionRule; + return this; + } + + public Builder color(@NotNull EnumWrappers.ChatFormatting color) { + Preconditions.checkNotNull(color); + this.color = color; + return this; + } + + public Builder options(int options) { + Preconditions.checkNotNull(collisionRule); + this.options = options; + return this; + } + + public WrappedTeamParameters build() { + Preconditions.checkNotNull(displayName, "Display name not set"); + Preconditions.checkNotNull(prefix, "Prefix not set"); + Preconditions.checkNotNull(suffix, "Suffix not set"); + Preconditions.checkNotNull(nametagVisibility, "Nametag visibility not set"); + Preconditions.checkNotNull(collisionRule, "Collision rule not set"); + Preconditions.checkNotNull(color, "Color not set"); + + // Not technically a packet, but it has a PacketDataSerializer constructor, so it works fine + Object handle = StructureCache.newPacket(getNmsClass()); + + WrappedTeamParameters wrapped = new WrappedTeamParameters(handle); + wrapped.writeComponent(0, displayName); + wrapped.writeComponent(1, prefix); + wrapped.writeComponent(2, suffix); + wrapped.modifier.withType(String.class).write(0, nametagVisibility); + wrapped.modifier.withType(String.class).write(1, collisionRule); + wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color)); + wrapped.modifier.withType(int.class).write(0, options); + return wrapped; + } + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java new file mode 100644 index 00000000..c8f371fb --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java @@ -0,0 +1,53 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import net.minecraft.EnumChatFormat; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class WrappedTeamParametersTest { + @BeforeAll + static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + void testTeamParameters() { + IChatBaseComponent displayName = IChatBaseComponent.b("display name"); + IChatBaseComponent prefix = IChatBaseComponent.b("prefix"); + IChatBaseComponent suffix = IChatBaseComponent.b("suffix"); + String nametagVisibility = "always"; + String collisionRule = "never"; + + WrappedTeamParameters wrapped = WrappedTeamParameters.newBuilder() + .displayName(WrappedChatComponent.fromHandle(displayName)) + .prefix(WrappedChatComponent.fromHandle(prefix)) + .suffix(WrappedChatComponent.fromHandle(suffix)) + .nametagVisibility(nametagVisibility) + .collisionRule(collisionRule) + .color(EnumWrappers.ChatFormatting.RED) + .options(1) + .build(); + + assertEquals(displayName, wrapped.getDisplayName().getHandle()); + assertEquals(prefix, wrapped.getPrefix().getHandle()); + assertEquals(suffix, wrapped.getSuffix().getHandle()); + assertEquals(nametagVisibility, wrapped.getNametagVisibility()); + assertEquals(collisionRule, wrapped.getCollisionRule()); + assertEquals(EnumWrappers.ChatFormatting.RED, wrapped.getColor()); + assertEquals(1, wrapped.getOptions()); + + PacketPlayOutScoreboardTeam.b handle = (PacketPlayOutScoreboardTeam.b) wrapped.getHandle(); + assertEquals(handle.a(), displayName); + assertEquals(handle.f(), prefix); + assertEquals(handle.g(), suffix); + assertEquals(handle.d(), nametagVisibility); + assertEquals(handle.e(), collisionRule); + assertEquals(handle.c(), EnumChatFormat.m); + assertEquals(handle.b(), 1); + } +}