diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index 234a6478..ee22bbfa 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -895,6 +895,17 @@ public abstract class AbstractStructure { return getOptionals(BukkitConverters.getWrappedTeamParametersConverter()); } + /** + * Retrieve a read/write structure for the NumberFormat class in 1.20.4+. + * @return A modifier for NumberFormat fields. + */ + public StructureModifier getNumberFormats() { + return structureModifier.withType( + MinecraftReflection.getNumberFormatClass().orElse(null), + BukkitConverters.getWrappedNumberFormatConverter()); + } + + /** * Retrieve a read/write structure for the MinecraftKey class. * @return A modifier for MinecraftKey fields. diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index d3d7d4a5..79303716 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1037,6 +1037,42 @@ public final class MinecraftReflection { return getMinecraftClass("network.chat.ChatModifier", "ChatModifier"); } + /** + * Retrieve the NMS NumberFormat class. + * + * @return The NumberFormat class. + */ + public static Optional> getNumberFormatClass() { + return getOptionalNMS("network.chat.numbers.NumberFormat"); + } + + /** + * Retrieve the NMS BlankFormat class. + * + * @return The FixedFormat class. + */ + public static Optional> getBlankFormatClass() { + return getOptionalNMS("network.chat.numbers.BlankFormat"); + } + + /** + * Retrieve the NMS FixedFormat class. + * + * @return The FixedFormat class. + */ + public static Optional> getFixedFormatClass() { + return getOptionalNMS("network.chat.numbers.FixedFormat"); + } + + /** + * Retrieve the NMS StyledFormat class. + * + * @return The StyledFormat class. + */ + public static Optional> getStyledFormatClass() { + return getOptionalNMS("network.chat.numbers.StyledFormat"); + } + /** * Retrieve the Gson class used by Minecraft. * diff --git a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index da3e5e90..0b29f2e1 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -644,6 +644,10 @@ public class BukkitConverters { return ignoreNull(handle(WrappedTeamParameters::getHandle, WrappedTeamParameters::new, WrappedTeamParameters.class)); } + public static EquivalentConverter getWrappedNumberFormatConverter() { + return ignoreNull(handle(WrappedNumberFormat::getHandle, WrappedNumberFormat::fromHandle, WrappedNumberFormat.class)); + } + public static EquivalentConverter getPacketContainerConverter() { return ignoreNull(handle(PacketContainer::getHandle, PacketContainer::fromPacket, PacketContainer.class)); } diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java index 241af075..0f4ff69b 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java @@ -46,10 +46,6 @@ public class WrappedComponentStyle extends AbstractWrapper { } } - public static WrappedComponentStyle fromHandle(Object handle) { - return new WrappedComponentStyle(handle); - } - public static WrappedComponentStyle fromJson(JsonObject json) { Object handle; if (CODEC != null) { diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedNumberFormat.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedNumberFormat.java new file mode 100644 index 00000000..bef76a79 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedNumberFormat.java @@ -0,0 +1,133 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import org.jetbrains.annotations.NotNull; + +/** + * A wrapper around the NumberFormat NMS classes. + * + * @author vytskalt + * @since 1.20.4 + */ +@SuppressWarnings("OptionalGetWithoutIsPresent") +public class WrappedNumberFormat extends AbstractWrapper { + private static final IllegalStateException UNSUPPORTED = new IllegalStateException("NumberFormat classes don't exist on this server version"); + private static final Object BLANK; + private static final ConstructorAccessor FIXED_CONSTRUCTOR, STYLED_CONSTRUCTOR; + + static { + if (!isSupported()) { + BLANK = null; + FIXED_CONSTRUCTOR = null; + STYLED_CONSTRUCTOR = null; + } else { + Class blankClass = MinecraftReflection.getBlankFormatClass().get(); + FuzzyReflection fuzzyBlank = FuzzyReflection.fromClass(blankClass, true); + BLANK = Accessors.getFieldAccessor(fuzzyBlank.getFieldByType("INSTANCE", blankClass)).get(null); + + FIXED_CONSTRUCTOR = Accessors.getConstructorAccessor( + MinecraftReflection.getFixedFormatClass().get(), + MinecraftReflection.getIChatBaseComponentClass() + ); + + STYLED_CONSTRUCTOR = Accessors.getConstructorAccessor( + MinecraftReflection.getStyledFormatClass().get(), + MinecraftReflection.getComponentStyleClass() + ); + } + } + + /** + * @return Whether the NumberFormat classes exist on the current server version + */ + public static boolean isSupported() { + return MinecraftReflection.getNumberFormatClass().isPresent(); + } + + public static WrappedNumberFormat fromHandle(Object handle) { + if (!isSupported()) { + throw UNSUPPORTED; + } + + if (MinecraftReflection.getBlankFormatClass().get().isInstance(handle)) { + return new Blank(handle); + } else if (MinecraftReflection.getFixedFormatClass().get().isInstance(handle)) { + return new Fixed(handle); + } else if (MinecraftReflection.getStyledFormatClass().get().isInstance(handle)) { + return new Styled(handle); + } else { + throw new IllegalArgumentException("handle is not a NumberFormat instance, but " + handle.getClass()); + } + } + + public static Blank blank() { + if (!isSupported()) { + throw UNSUPPORTED; + } + + return new Blank(WrappedNumberFormat.BLANK); + } + + public static Fixed fixed(@NotNull WrappedChatComponent content) { + if (!isSupported()) { + throw UNSUPPORTED; + } + + Object handle = FIXED_CONSTRUCTOR.invoke(content.getHandle()); + return new Fixed(handle); + } + + public static Styled styled(@NotNull WrappedComponentStyle style) { + if (!isSupported()) { + throw UNSUPPORTED; + } + + Object handle = STYLED_CONSTRUCTOR.invoke(style.getHandle()); + return new Styled(handle); + } + + private WrappedNumberFormat(Class handleType) { + super(handleType); + } + + public static class Blank extends WrappedNumberFormat { + private Blank(Object handle) { + super(MinecraftReflection.getBlankFormatClass().get()); + setHandle(handle); + } + } + + public static class Fixed extends WrappedNumberFormat { + private final StructureModifier modifier; + + private Fixed(Object handle) { + super(MinecraftReflection.getFixedFormatClass().get()); + setHandle(handle); + this.modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); + } + + public WrappedChatComponent getContent() { + Object handle = modifier.withType(MinecraftReflection.getIChatBaseComponentClass()).read(0); + return WrappedChatComponent.fromHandle(handle); + } + } + + public static class Styled extends WrappedNumberFormat { + private final StructureModifier modifier; + + private Styled(Object handle) { + super(MinecraftReflection.getStyledFormatClass().get()); + setHandle(handle); + this.modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); + } + + public WrappedComponentStyle getStyle() { + Object handle = modifier.withType(MinecraftReflection.getComponentStyleClass()).read(0); + return new WrappedComponentStyle(handle); + } + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedNumberFormatTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedNumberFormatTest.java new file mode 100644 index 00000000..c8b417f8 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedNumberFormatTest.java @@ -0,0 +1,50 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import net.minecraft.EnumChatFormat; +import net.minecraft.network.chat.ChatModifier; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.chat.numbers.BlankFormat; +import net.minecraft.network.chat.numbers.FixedFormat; +import net.minecraft.network.chat.numbers.StyledFormat; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +public class WrappedNumberFormatTest { + + @BeforeAll + static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + void testBlankFormat() { + assertInstanceOf(WrappedNumberFormat.Blank.class, WrappedNumberFormat.fromHandle(BlankFormat.a)); + assertEquals(BlankFormat.a, WrappedNumberFormat.blank().getHandle()); + } + + @Test + void testFixedFormat() { + IChatBaseComponent content = IChatBaseComponent.a("Fixed"); + WrappedNumberFormat wrappedHandle = WrappedNumberFormat.fromHandle(new FixedFormat(content)); + assertInstanceOf(WrappedNumberFormat.Fixed.class, wrappedHandle); + assertEquals(content, ((WrappedNumberFormat.Fixed) wrappedHandle).getContent().getHandle()); + + WrappedNumberFormat.Fixed wrapped = WrappedNumberFormat.fixed(WrappedChatComponent.fromHandle(content)); + assertEquals(content, wrapped.getContent().getHandle()); + } + + @Test + void testStyledFormat() { + ChatModifier style = ChatModifier.a.b(EnumChatFormat.g); + WrappedNumberFormat wrappedHandle = WrappedNumberFormat.fromHandle(new StyledFormat(style)); + assertInstanceOf(WrappedNumberFormat.Styled.class, wrappedHandle); + assertEquals(style, ((WrappedNumberFormat.Styled) wrappedHandle).getStyle().getHandle()); + + WrappedNumberFormat.Styled newWrapper = WrappedNumberFormat.styled(new WrappedComponentStyle(style)); + assertEquals(style, newWrapper.getStyle().getHandle()); + } +}